Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LethalDiagnostics v1.0.0
LethalDiagnostics.dll
Decompiled 2 hours agousing 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 <DebouncedWriteLoop>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DebouncedWriteLoop>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(<DebouncedWriteLoop>d__7))] private IEnumerator DebouncedWriteLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DebouncedWriteLoop>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<string> LoadedPluginsList = new List<string>(); private static readonly List<string> SceneLoadHistory = new List<string>(); private static string _currentLoadingScene = "Aucune (Menu Principal)"; private static DateTime _sceneLoadStartTime = DateTime.MinValue; private static readonly HashSet<string> CriticalExceptions = new HashSet<string>(); private static readonly HashSet<string> MissingComponents = new HashSet<string>(); private static readonly HashSet<string> MapGenerationAlerts = new HashSet<string>(); private static readonly HashSet<string> NetworkSyncAlerts = new HashSet<string>(); private static readonly HashSet<string> MimicDiagnostics = new HashSet<string>(); private static readonly HashSet<string> ModInterferences = new HashSet<string>(); private static readonly Dictionary<string, int> ExceptionCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> MissingComponentCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> MapGenAlertCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> NetworkSyncCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> MimicDiagCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> ModInterferenceCounts = new Dictionary<string, int>(); 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(); } } }