using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalDiagnostics")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LethalDiagnostics")] [assembly: AssemblyTitle("LethalDiagnostics")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 LethalDiagnostics { [BepInPlugin("com.nadre.lethaldiagnostics", "LethalDiagnostics", "1.0.0")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__7 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; try { DiagnosticsManager.WriteReportDebounced(); } catch { } break; } <>2__current = (object)new WaitForSeconds(5f); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string ModGUID = "com.nadre.lethaldiagnostics"; public const string ModName = "LethalDiagnostics"; public const string ModVersion = "1.0.0"; private readonly Harmony harmony = new Harmony("com.nadre.lethaldiagnostics"); internal static Plugin Instance; internal static ManualLogSource LoggerInstance; private void Awake() { Instance = this; LoggerInstance = ((BaseUnityPlugin)this).Logger; LoggerInstance.LogInfo((object)"LethalDiagnostics v1.0.0 en cours de chargement..."); Logger.Listeners.Add((ILogListener)(object)new DiagnosticsLogListener()); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; harmony.PatchAll(typeof(Plugin).Assembly); DiagnosticsManager.InitializeReport(); ((MonoBehaviour)this).StartCoroutine(DebouncedWriteLoop()); LoggerInstance.LogInfo((object)"LethalDiagnostics v1.0.0 initialisé avec succès."); } [IteratorStateMachine(typeof(d__7))] private IEnumerator DebouncedWriteLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(0) { <>4__this = this }; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { DiagnosticsManager.OnSceneLoaded(((Scene)(ref scene)).name); } private void OnSceneUnloaded(Scene scene) { DiagnosticsManager.OnSceneUnloaded(((Scene)(ref scene)).name); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; try { DiagnosticsManager.ForceWriteReport(); } catch { } } } public class DiagnosticsLogListener : ILogListener, IDisposable { public void LogEvent(object sender, LogEventArgs eventArgs) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { DiagnosticsManager.AnalyzeLog(eventArgs.Source.SourceName, eventArgs.Level, eventArgs.Data?.ToString()); } catch { } } public void Dispose() { } } public static class DiagnosticsManager { private static readonly object _lock = new object(); private static readonly List LoadedPluginsList = new List(); private static readonly List SceneLoadHistory = new List(); private static string _currentLoadingScene = "Aucune (Menu Principal)"; private static DateTime _sceneLoadStartTime = DateTime.MinValue; private static readonly HashSet CriticalExceptions = new HashSet(); private static readonly HashSet MissingComponents = new HashSet(); private static readonly HashSet MapGenerationAlerts = new HashSet(); private static readonly HashSet NetworkSyncAlerts = new HashSet(); private static readonly HashSet MimicDiagnostics = new HashSet(); private static readonly HashSet ModInterferences = new HashSet(); private static readonly Dictionary ExceptionCounts = new Dictionary(); private static readonly Dictionary MissingComponentCounts = new Dictionary(); private static readonly Dictionary MapGenAlertCounts = new Dictionary(); private static readonly Dictionary NetworkSyncCounts = new Dictionary(); private static readonly Dictionary MimicDiagCounts = new Dictionary(); private static readonly Dictionary ModInterferenceCounts = new Dictionary(); private static bool _needsWrite = false; private static bool _isWriting = false; private static readonly object _writeLock = new object(); public static string GetReportPath() { try { string text = Path.Combine(Paths.BepInExRootPath, "cache", "LethalDiagnostics"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } return Path.Combine(text, "LethalDiagnosticsReport.md"); } catch { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LethalDiagnosticsReport.md"); } } public static void InitializeReport() { lock (_lock) { ScanLoadedPlugins(); RequestWrite(); ForceWriteReport(); } } public static void ScanLoadedPlugins() { lock (_lock) { LoadedPluginsList.Clear(); try { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string name = value.Metadata.Name; string text = value.Metadata.Version.ToString(); string gUID = value.Metadata.GUID; LoadedPluginsList.Add("- **" + name + "** (`" + gUID + "`) - v" + text); } } catch (Exception ex) { LoadedPluginsList.Add("- *Erreur lors du scan des plugins : " + ex.Message + "*"); } } } public static void OnSceneLoaded(string sceneName) { lock (_lock) { if (!(sceneName == "InitScene")) { if (_sceneLoadStartTime != DateTime.MinValue) { TimeSpan timeSpan = DateTime.Now - _sceneLoadStartTime; string arg = ((timeSpan.TotalSeconds > 10.0) ? $"⚠\ufe0f **{timeSpan.TotalSeconds:F2}s** (Lent - potentiel goulot d'étranglement)" : $"**{timeSpan.TotalSeconds:F2}s** (Normal)"); SceneLoadHistory.Add($"- **{sceneName}** : Chargé en {arg} [Le {DateTime.Now:HH:mm:ss}]"); } else { SceneLoadHistory.Add($"- **{sceneName}** : Chargé directement [Le {DateTime.Now:HH:mm:ss}]"); } _currentLoadingScene = sceneName; _sceneLoadStartTime = DateTime.MinValue; RequestWrite(); } } } public static void OnSceneUnloaded(string sceneName) { lock (_lock) { _currentLoadingScene = "Chargement en cours depuis " + sceneName + "..."; _sceneLoadStartTime = DateTime.Now; } } public static void AnalyzeLog(string source, LogLevel level, string? message) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Invalid comparison between Unknown and I4 //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0353: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Invalid comparison between Unknown and I4 //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Invalid comparison between Unknown and I4 //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Invalid comparison between Unknown and I4 //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Invalid comparison between Unknown and I4 //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Invalid comparison between Unknown and I4 if (string.IsNullOrEmpty(message)) { return; } lock (_lock) { if (message.Contains("Scene that began loading:")) { _sceneLoadStartTime = DateTime.Now; return; } if ((message.Contains("RPC") || message.Contains("packet") || message.Contains("desync") || message.Contains("NetworkVariable") || message.Contains("client received") || message.Contains("NetworkBehaviour") || message.Contains("loot") || message.Contains("item")) && (message.Contains("NetworkVariable") || message.Contains("RPC") || message.Contains("desync") || message.Contains("packet loss") || message.Contains("Lag") || (int)level == 4 || (int)level == 2)) { string text = "[" + source + "] " + GetFirstLine(message); if (!NetworkSyncAlerts.Contains(text)) { NetworkSyncAlerts.Add(text); NetworkSyncCounts[text] = 1; } else { NetworkSyncCounts[text]++; } RequestWrite(); } if ((source.Contains("Mirage") || source.Contains("Masked") || message.Contains("MaskedPlayerEnemy") || message.Contains("mimic") || message.Contains("Zombie") || message.Contains("voice") || message.Contains("Dissonance") || source.Contains("Dissonance")) && (message.Contains("spawn") || message.Contains("kill") || message.Contains("voice") || message.Contains("sync") || message.Contains("mimic") || message.Contains("recording") || message.Contains("buffer") || (int)level == 4 || (int)level == 2)) { string text2 = "[" + source + "] " + GetFirstLine(message); if (!MimicDiagnostics.Contains(text2)) { MimicDiagnostics.Add(text2); MimicDiagCounts[text2] = 1; } else { MimicDiagCounts[text2]++; } RequestWrite(); } if ((source.Contains("MoreShipUpgrades") || source.Contains("DungeonPlus") || source.Contains("LethalLevelLoader") || message.Contains("MoreShipUpgrades") || message.Contains("DungeonPlus") || message.Contains("LethalLevelLoader") || message.Contains("compatibility") || message.Contains("conflict") || message.Contains("override")) && ((int)level == 4 || (int)level == 2 || message.Contains("conflict") || message.Contains("failed"))) { string text3 = "[" + source + "] " + GetFirstLine(message); if (!ModInterferences.Contains(text3)) { ModInterferences.Add(text3); ModInterferenceCounts[text3] = 1; } else { ModInterferenceCounts[text3]++; } RequestWrite(); } if ((message.Contains("Exception") || message.Contains("Error") || message.Contains("Reference not set") || (int)level == 2) && (message.Contains("NullReferenceException") || message.Contains("MissingMethodException") || message.Contains("TypeLoadException") || message.Contains("ReflectionTypeLoadException") || message.Contains("IndexOutOfRangeException") || message.Contains("Stack overflow") || message.Contains("ArgumentNullException"))) { string text4 = "Exception Générique"; Match match = Regex.Match(message, "\\w+Exception"); if (match.Success) { text4 = match.Value; } string firstLine = GetFirstLine(message); string text5 = "[" + text4 + "] " + firstLine + " (Source: " + source + ")"; if (!CriticalExceptions.Contains(text5)) { CriticalExceptions.Add(text5); ExceptionCounts[text5] = 1; } else { ExceptionCounts[text5]++; } RequestWrite(); } else if ((message.Contains("missing") || message.Contains("referenced script") || message.Contains("could not be instantiated")) && (message.Contains("referenced script") || message.Contains("Behaviour is missing") || message.Contains("Game Object"))) { string text6 = message; Match match2 = Regex.Match(message, "Game Object '([^']+)'"); text6 = ((!match2.Success) ? GetFirstLine(message) : ("Script manquant sur l'objet '" + match2.Groups[1].Value + "' (Signalé par " + source + ")")); if (!MissingComponents.Contains(text6)) { MissingComponents.Add(text6); MissingComponentCounts[text6] = 1; } else { MissingComponentCounts[text6]++; } RequestWrite(); } else if ((message.Contains("RuntimeNavMeshBuilder") || message.Contains("Dungeon") || message.Contains("NavMesh")) && (message.Contains("skipped because it does not allow read access") || message.Contains("RuntimeDungeon component missing") || message.Contains("Dungeon flow") || message.Contains("failed to generate"))) { string text7 = "[" + source + "] " + GetFirstLine(message); if (!MapGenerationAlerts.Contains(text7)) { MapGenerationAlerts.Add(text7); MapGenAlertCounts[text7] = 1; } else { MapGenAlertCounts[text7]++; } RequestWrite(); } } } private static string GetFirstLine(string text) { if (string.IsNullOrEmpty(text)) { return string.Empty; } int num = text.IndexOf('\n'); if (num > 0) { return text.Substring(0, num).Trim(); } return text.Trim(); } public static void RequestWrite() { lock (_lock) { _needsWrite = true; } } public static void WriteReportDebounced() { lock (_writeLock) { if (!_needsWrite || _isWriting) { return; } _isWriting = true; _needsWrite = false; } string reportContent; lock (_lock) { reportContent = GenerateReportContent(); } Task.Run(delegate { try { string reportPath = GetReportPath(); string directoryName = Path.GetDirectoryName(reportPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(reportPath, reportContent, Encoding.UTF8); } catch { } finally { lock (_writeLock) { _isWriting = false; } } }); } public static void ForceWriteReport() { lock (_lock) { try { string contents = GenerateReportContent(); string reportPath = GetReportPath(); string directoryName = Path.GetDirectoryName(reportPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(reportPath, contents, Encoding.UTF8); } catch { } } } private static string GetSessionStatus() { if ((Object)(object)StartOfRound.Instance == (Object)null) { return "- **Statut de session :** `En attente du lancement d'une partie (Menu Principal)`\n"; } string text = (((Object)(object)StartOfRound.Instance.currentLevel != (Object)null) ? StartOfRound.Instance.currentLevel.PlanetName : "Orbite / Vaisseau"); int num = 0; if (StartOfRound.Instance.allPlayerScripts != null) { PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val != (Object)null && val.isPlayerControlled) { num++; } } } string text2 = "Inconnu"; if (StartOfRound.Instance.allPlayerScripts != null && StartOfRound.Instance.allPlayerScripts.Length != 0 && (Object)(object)StartOfRound.Instance.allPlayerScripts[0] != (Object)null) { text2 = StartOfRound.Instance.allPlayerScripts[0].playerUsername; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("- **Lune active :** `" + text + "`"); stringBuilder.AppendLine($"- **Joueurs actifs contrôlés :** `{num}`"); stringBuilder.AppendLine("- **Nom de l'Hôte (Host) :** `" + text2 + "`"); stringBuilder.AppendLine("- **Scène active Unity :** `" + _currentLoadingScene + "`"); return stringBuilder.ToString(); } private static string GenerateReportContent() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# \ud83d\udee0\ufe0f Rapport de Diagnostics & Analyse de Bugs (Lethal Company)"); stringBuilder.AppendLine(); stringBuilder.AppendLine("*Généré dynamiquement par le mod **LethalDiagnostics** dans le cache du profil actif.* "); stringBuilder.AppendLine($"*Dernière mise à jour : {DateTime.Now:dd/MM/yyyy à HH:mm:ss}* "); stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## ⏱\ufe0f Statut de Session & Temps de Chargement"); stringBuilder.AppendLine(); stringBuilder.AppendLine(GetSessionStatus()); stringBuilder.AppendLine("### Historique de navigation des lunes :"); if (SceneLoadHistory.Count == 0) { stringBuilder.AppendLine("*Aucune lune chargée pour le moment. En attente du décollage depuis l'orbite...*"); } else { foreach (string item in SceneLoadHistory) { stringBuilder.AppendLine(item); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83c\udfad Diagnostics des Mimics & Voix (Mirage / Masked)"); stringBuilder.AppendLine("Suivi de l'apparition des mimics, de l'imitation vocale et des alertes de micro / tampon audio."); stringBuilder.AppendLine(); if (MimicDiagnostics.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **Aucun diagnostic de Mimic.** Mirage et les autres systèmes de voix n'ont pas encore signalé d'anomalies."); } else { foreach (string mimicDiagnostic in MimicDiagnostics) { int num = MimicDiagCounts[mimicDiagnostic]; stringBuilder.AppendLine($"- \ud83d\udc64 `{mimicDiagnostic}` (**x{num}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83d\udea8 Alertes de Synchronisation Réseau, RPC & Desync de Loot"); stringBuilder.AppendLine("Ce panneau regroupe les pertes de paquets, les retards d'RPC et les décalages de variables réseau pouvant provoquer des objets flottants ou des desyncs entre joueurs."); stringBuilder.AppendLine(); if (NetworkSyncAlerts.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **Réseau stable.** Aucune anomalie d'RPC, perte de paquet majeure ou désynchronisation de loot n'a été détectée."); } else { foreach (string networkSyncAlert in NetworkSyncAlerts) { int num2 = NetworkSyncCounts[networkSyncAlert]; stringBuilder.AppendLine($"- \ud83d\udce1 `{networkSyncAlert}` (**x{num2}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83c\udf9b\ufe0f Diagnostics d'Interférences entre Mods (LGU, DungeonPlus, LLL)"); stringBuilder.AppendLine("Affiche les conflits d'injection de scripts, les surcharges d'assets ou les erreurs de compatibilité liées aux upgrades et intérieurs."); stringBuilder.AppendLine(); if (ModInterferences.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **Compatibilité optimale.** Aucun conflit majeur signalé par MoreShipUpgrades, DungeonPlus ou LethalLevelLoader."); } else { foreach (string modInterference in ModInterferences) { int num3 = ModInterferenceCounts[modInterference]; stringBuilder.AppendLine($"- ⚠\ufe0f `{modInterference}` (**x{num3}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83d\uded1 Exceptions Critiques & Erreurs de Code"); stringBuilder.AppendLine("Erreurs majeures de programmation (NullReference, MissingMethod) pouvant causer des plantages ou des freezes d'interface."); stringBuilder.AppendLine(); if (CriticalExceptions.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **Aucune exception critique détectée.** Tous les scripts et liaisons réseau de vos mods fonctionnent normalement."); } else { foreach (string criticalException in CriticalExceptions) { int num4 = ExceptionCounts[criticalException]; stringBuilder.AppendLine($"- \ud83d\uded1 `{criticalException}` (**x{num4}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83e\udde9 Composants ou Scripts Manquants"); stringBuilder.AppendLine("Indique si des cartes ou des éléments de scrap tentent d'instancier des composants absents de vos dossiers."); stringBuilder.AppendLine(); if (MissingComponents.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **Aucune dépendance manquante.** Tous les objets et éléments du donjon sont correctement résolus."); } else { foreach (string missingComponent in MissingComponents) { int num5 = MissingComponentCounts[missingComponent]; stringBuilder.AppendLine($"- \ud83e\udde9 {missingComponent} (**x{num5}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83d\uddfa\ufe0f Anomalies de Génération de Donjon (DunGen / NavMesh)"); stringBuilder.AppendLine("Erreurs bloquant les chemins de navigation des monstres ou empêchant la liaison des dalles."); stringBuilder.AppendLine(); if (MapGenerationAlerts.Count == 0) { stringBuilder.AppendLine("> [!NOTE]\n> **NavMesh et Donjon optimaux.** L'IA des monstres peut circuler normalement et les dalles se sont générées correctement."); } else { foreach (string mapGenerationAlert in MapGenerationAlerts) { int num6 = MapGenAlertCounts[mapGenerationAlert]; stringBuilder.AppendLine($"- \ud83c\udf9b\ufe0f {mapGenerationAlert} (**x{num6}**)"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("---"); stringBuilder.AppendLine(); stringBuilder.AppendLine("## \ud83d\udd0c Fiche Technique : Liste des Mods Actifs (Chainloader BepInEx)"); stringBuilder.AppendLine("Copie cette liste si tu dois partager ton diagnostic avec ton groupe ou le créateur d'un mod."); stringBuilder.AppendLine(); if (LoadedPluginsList.Count == 0) { stringBuilder.AppendLine("*Aucun plugin actif détecté ou scan non complété.*"); } else { foreach (string loadedPlugins in LoadedPluginsList) { stringBuilder.AppendLine(loadedPlugins); } } return stringBuilder.ToString(); } } }