Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of MLVScan v1.6.0
Plugins/MLVScan.MelonLoader.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using MLVScan; using MLVScan.Abstractions; using MLVScan.Adapters; using MLVScan.Models; using MLVScan.Models.Rules; using MLVScan.Services; using MLVScan.Services.Helpers; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "MLVScan", "1.6.0", "Bars", null)] [assembly: MelonPriority(int.MinValue)] [assembly: MelonColor(255, 139, 0, 0)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MLVScan.MelonLoader")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.6.0")] [assembly: AssemblyInformationalVersion("1.0.0+580829955f1ad2ee1bc299a401e233e1572a9d1f")] [assembly: AssemblyProduct("MLVScan.MelonLoader")] [assembly: AssemblyTitle("MLVScan.MelonLoader")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.6.0.0")] [module: UnverifiableCode] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MLVScan { public class Core : MelonPlugin { private ServiceFactory _serviceFactory; private ConfigManager _configManager; private ModScanner _modScanner; private ModDisabler _modDisabler; private IlDumpService _ilDumpService; private DeveloperReportGenerator _developerReportGenerator; private bool _initialized; private static readonly string[] DefaultWhitelistedHashes = new string[4] { "3918e1454e05de4dd3ace100d8f4d53936c9b93694dbff5bcc0293d689cb0ab7", "8e6dd1943c80e2d1472a9dc2c6722226d961027a7ec20aab9ad8f1184702d138", "d47eb6eabd3b6e3b742c7d9693651bc3a61a90dcbe838f9a4276953089ee4951", "cfe43c0d285867a5701d96de1edd25cb02725fe2629b88386351dc07b11a08b5" }; public override void OnEarlyInitializeMelon() { try { ((MelonBase)this).LoggerInstance.Msg("Pre-scanning for malicious mods..."); _serviceFactory = new ServiceFactory(((MelonBase)this).LoggerInstance); _configManager = _serviceFactory.CreateConfigManager(); InitializeDefaultWhitelist(); _modScanner = _serviceFactory.CreateModScanner(); _modDisabler = _serviceFactory.CreateModDisabler(); _ilDumpService = _serviceFactory.CreateIlDumpService(); _developerReportGenerator = _serviceFactory.CreateDeveloperReportGenerator(); _initialized = true; ScanAndDisableMods(force: true); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error in pre-mod scan: " + ex.Message); ((MelonBase)this).LoggerInstance.Error(ex.StackTrace); } } public override void OnInitializeMelon() { try { ((MelonBase)this).LoggerInstance.Msg("MLVScan initialization complete"); if (_configManager.Config.WhitelistedHashes.Length != 0) { ((MelonBase)this).LoggerInstance.Msg($"{_configManager.Config.WhitelistedHashes.Length} mod(s) are whitelisted and won't be scanned"); ((MelonBase)this).LoggerInstance.Msg("To manage whitelisted mods, edit MelonPreferences.cfg"); } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error initializing MLVScan: " + ex.Message); ((MelonBase)this).LoggerInstance.Error(ex.StackTrace); } } private void InitializeDefaultWhitelist() { if (_configManager != null && _configManager.GetWhitelistedHashes().Length == 0) { ((MelonBase)this).LoggerInstance.Msg("Initializing default whitelist"); _configManager.SetWhitelistedHashes(DefaultWhitelistedHashes); } } public Dictionary<string, List<ScanFinding>> ScanAndDisableMods(bool force = false) { try { if (!_initialized) { ((MelonBase)this).LoggerInstance.Error("Cannot scan mods - MLVScan not properly initialized"); return new Dictionary<string, List<ScanFinding>>(); } ((MelonBase)this).LoggerInstance.Msg("Scanning for suspicious mods..."); Dictionary<string, List<ScanFinding>> dictionary = (from kv in _modScanner.ScanAllMods(force) where kv.Value.Count > 0 && kv.Value.Any((ScanFinding f) => f.Location != "Assembly scanning") select kv).ToDictionary((KeyValuePair<string, List<ScanFinding>> kv) => kv.Key, (KeyValuePair<string, List<ScanFinding>> kv) => kv.Value); if (dictionary.Count > 0) { List<DisabledModInfo> list = _modDisabler.DisableSuspiciousMods(dictionary, force); int count = list.Count; ((MelonBase)this).LoggerInstance.Msg($"Disabled {count} suspicious mods"); if (count <= 0) { return dictionary; } GenerateDetailedReports(list, dictionary); ((MelonBase)this).LoggerInstance.Msg("To whitelist any false positives, add their SHA256 hash to the MLVScan → WhitelistedHashes setting in MelonPreferences.cfg"); } else { ((MelonBase)this).LoggerInstance.Msg("No suspicious mods found"); } return dictionary; } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Error scanning mods: " + ex.Message); return new Dictionary<string, List<ScanFinding>>(); } } private void GenerateDetailedReports(List<DisabledModInfo> disabledMods, Dictionary<string, List<ScanFinding>> scanResults) { bool valueOrDefault = (_configManager?.Config?.DeveloperMode).GetValueOrDefault(); if (valueOrDefault) { ((MelonBase)this).LoggerInstance.Msg("Developer Mode: Enabled"); } ((MelonBase)this).LoggerInstance.Warning("======= DETAILED SCAN REPORT ======="); foreach (DisabledModInfo disabledMod in disabledMods) { string fileName = Path.GetFileName(disabledMod.OriginalPath); string fileHash = disabledMod.FileHash; string text = (File.Exists(disabledMod.DisabledPath) ? disabledMod.DisabledPath : disabledMod.OriginalPath); ((MelonBase)this).LoggerInstance.Warning("SUSPICIOUS MOD: " + fileName); ((MelonBase)this).LoggerInstance.Msg("SHA256 Hash: " + fileHash); ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); if (!scanResults.TryGetValue(disabledMod.OriginalPath, out var value)) { ((MelonBase)this).LoggerInstance.Error("Could not find scan results for disabled mod"); continue; } List<ScanFinding> list = value.Where((ScanFinding f) => f.Location != "Assembly scanning").ToList(); if (list.Count == 0) { ((MelonBase)this).LoggerInstance.Msg("No specific suspicious patterns were identified."); continue; } Dictionary<string, List<ScanFinding>> dictionary = (from f in list group f by f.Description).ToDictionary((IGrouping<string, ScanFinding> g) => g.Key, (IGrouping<string, ScanFinding> g) => g.ToList()); ((MelonBase)this).LoggerInstance.Warning($"Total suspicious patterns found: {list.Count}"); Dictionary<Severity, int> dictionary2 = (from f in list group f by f.Severity into g orderby (int)g.Key descending select g).ToDictionary((IGrouping<Severity, ScanFinding> g) => g.Key, (IGrouping<Severity, ScanFinding> g) => g.Count()); ((MelonBase)this).LoggerInstance.Warning("Severity breakdown:"); foreach (KeyValuePair<Severity, int> item in dictionary2) { string arg = FormatSeverityLabel(item.Key); ((MelonBase)this).LoggerInstance.Msg($" {arg}: {item.Value} issue(s)"); } ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); if (valueOrDefault && _developerReportGenerator != null) { _developerReportGenerator.GenerateConsoleReport(fileName, list); } else { ((MelonBase)this).LoggerInstance.Warning("Suspicious patterns found:"); foreach (KeyValuePair<string, List<ScanFinding>> item2 in dictionary) { item2.Deconstruct(out var key, out var value2); string arg2 = key; List<ScanFinding> list2 = value2; string arg3 = FormatSeverityLabel(list2[0].Severity); ((MelonBase)this).LoggerInstance.Warning($"[{arg3}] {arg2} ({list2.Count} instances)"); int num = Math.Min(list2.Count, 3); for (int i = 0; i < num; i++) { ScanFinding scanFinding = list2[i]; ((MelonBase)this).LoggerInstance.Msg(" * At: " + scanFinding.Location); if (!string.IsNullOrEmpty(scanFinding.CodeSnippet)) { ((MelonBase)this).LoggerInstance.Msg(" Code Snippet (IL):"); string[] array = scanFinding.CodeSnippet.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { ((MelonBase)this).LoggerInstance.Msg(" " + text2); } } } if (list2.Count > num) { ((MelonBase)this).LoggerInstance.Msg($" * And {list2.Count - num} more instances..."); } ((MelonBase)this).LoggerInstance.Msg(""); } ((MelonBase)this).LoggerInstance.Msg("-------------------------------"); } DisplaySecurityNotice(fileName); try { string text3 = Path.Combine(MelonEnvironment.UserDataDirectory, "MLVScan", "Reports"); if (!Directory.Exists(text3)) { Directory.CreateDirectory(text3); } string text4 = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string text5 = Path.Combine(text3, fileName + "_" + text4 + ".report.txt"); string text6 = Path.Combine(text3, "Prompts"); if (!Directory.Exists(text6)) { Directory.CreateDirectory(text6); } ConfigManager configManager = _configManager; if (configManager != null && (configManager.Config?.DumpFullIlReports).GetValueOrDefault() && _ilDumpService != null) { string text7 = Path.Combine(Path.Combine(text3, "IL"), fileName + "_" + text4 + ".il.txt"); if (_ilDumpService.TryDumpAssembly(text, text7)) { ((MelonBase)this).LoggerInstance.Msg("Full IL dump saved to: " + text7); } else { ((MelonBase)this).LoggerInstance.Warning("Failed to dump IL for this mod (see logs for details)."); } } PromptGeneratorService promptGeneratorService = _serviceFactory.CreatePromptGenerator(); using (StreamWriter streamWriter = new StreamWriter(text5)) { if (valueOrDefault && _developerReportGenerator != null) { string value3 = _developerReportGenerator.GenerateFileReport(fileName, fileHash, list); streamWriter.Write(value3); } else { streamWriter.WriteLine("MLVScan Security Report"); streamWriter.WriteLine($"Generated: {DateTime.Now}"); streamWriter.WriteLine("Mod File: " + fileName); streamWriter.WriteLine("SHA256 Hash: " + fileHash); streamWriter.WriteLine("Original Path: " + disabledMod.OriginalPath); streamWriter.WriteLine("Disabled Path: " + disabledMod.DisabledPath); streamWriter.WriteLine("Path Used For Analysis: " + text); streamWriter.WriteLine($"Total Suspicious Patterns: {list.Count}"); streamWriter.WriteLine("\nSeverity Breakdown:"); foreach (KeyValuePair<Severity, int> item3 in dictionary2) { streamWriter.WriteLine($"- {item3.Key}: {item3.Value} issue(s)"); } streamWriter.WriteLine("=============================================="); foreach (KeyValuePair<string, List<ScanFinding>> item4 in dictionary) { streamWriter.WriteLine("\n== " + item4.Key + " =="); streamWriter.WriteLine($"Severity: {item4.Value[0].Severity}"); streamWriter.WriteLine($"Instances: {item4.Value.Count}"); streamWriter.WriteLine("\nLocations & Snippets:"); foreach (ScanFinding item5 in item4.Value) { streamWriter.WriteLine("- " + item5.Location); if (!string.IsNullOrEmpty(item5.CodeSnippet)) { streamWriter.WriteLine(" Code Snippet (IL):"); string[] array = item5.CodeSnippet.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text8 in array) { streamWriter.WriteLine(" " + text8); } streamWriter.WriteLine(); } } } WriteSecurityNoticeToReport(streamWriter); } } if (promptGeneratorService.SavePromptToFile(text, list, text6)) { ((MelonBase)this).LoggerInstance.Msg("Detailed report saved to: " + text5); ((MelonBase)this).LoggerInstance.Msg("LLM analysis prompt saved to: " + Path.Combine(text6, fileName + ".prompt.md")); ((MelonBase)this).LoggerInstance.Msg("You can copy the contents of the prompt file into ChatGPT to help determine if this is malware or a false positive, although don't trust ChatGPT to be 100% accurate."); } else { ((MelonBase)this).LoggerInstance.Msg("Detailed report saved to: " + text5); } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Failed to save detailed report: " + ex.Message); } } ((MelonBase)this).LoggerInstance.Warning("====== END OF SCAN REPORT ======"); } private static string FormatSeverityLabel(Severity severity) { return severity switch { Severity.Critical => "CRITICAL", Severity.High => "HIGH", Severity.Medium => "MEDIUM", Severity.Low => "LOW", _ => severity.ToString().ToUpper(), }; } private void DisplaySecurityNotice(string modName) { ((MelonBase)this).LoggerInstance.Warning("IMPORTANT SECURITY NOTICE"); ((MelonBase)this).LoggerInstance.Msg("MLVScan has detected and disabled " + modName + " before it was loaded."); ((MelonBase)this).LoggerInstance.Msg("If this is your first time running the game with this mod, your PC is likely safe."); ((MelonBase)this).LoggerInstance.Msg("However, if you've previously run the game with this mod, your system MAY be infected."); ((MelonBase)this).LoggerInstance.Msg("Keep in mind that no detection system is perfect, and this mod may be falsely flagged."); ((MelonBase)this).LoggerInstance.Warning("Recommended security steps:"); ((MelonBase)this).LoggerInstance.Msg("1. Check with the modding community first - no detection is perfect"); ((MelonBase)this).LoggerInstance.Msg(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); ((MelonBase)this).LoggerInstance.Msg(" Ask about this mod in the MLVScan thread of #mod-releases to confirm if it's actually malicious"); ((MelonBase)this).LoggerInstance.Msg("2. Run a full system scan with a trusted antivirus like Malwarebytes"); ((MelonBase)this).LoggerInstance.Msg(" Malwarebytes is recommended as a free and effective antivirus solution"); ((MelonBase)this).LoggerInstance.Msg("3. Use Microsoft Safety Scanner for a secondary scan"); ((MelonBase)this).LoggerInstance.Msg("4. Change important passwords if antivirus shows a threat"); ((MelonBase)this).LoggerInstance.Warning("Resources for malware removal:"); ((MelonBase)this).LoggerInstance.Msg("- Malwarebytes: https://www.malwarebytes.com/cybersecurity/basics/how-to-remove-virus-from-computer"); ((MelonBase)this).LoggerInstance.Msg("- Microsoft Safety Scanner: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); } private static void WriteSecurityNoticeToReport(StreamWriter writer) { writer.WriteLine("\n\n============== SECURITY NOTICE ==============\n"); writer.WriteLine("IMPORTANT: READ THIS SECURITY INFORMATION\n"); writer.WriteLine("MLVScan has detected and disabled this mod before it was loaded."); writer.WriteLine("This mod contains code patterns commonly associated with malware.\n"); writer.WriteLine("YOUR SYSTEM SECURITY STATUS:"); writer.WriteLine("- If this is your FIRST TIME starting the game with this mod installed:"); writer.WriteLine(" Your PC is likely SAFE as MLVScan prevented the mod from loading."); writer.WriteLine("\n- If you have PREVIOUSLY PLAYED the game with this mod loaded:"); writer.WriteLine(" Your system MAY BE INFECTED with malware. Take action immediately.\n"); writer.WriteLine("RECOMMENDED SECURITY STEPS:"); writer.WriteLine("1. Check with the modding community first - no detection system is perfect"); writer.WriteLine(" Join the modding Discord at: https://discord.gg/UD4K4chKak"); writer.WriteLine(" Ask about this mod in the #MLVScan or #report-mods channels to confirm if it's actually malicious"); writer.WriteLine("\n2. Run a full system scan with a reputable antivirus program"); writer.WriteLine(" Free option: Malwarebytes (https://www.malwarebytes.com/)"); writer.WriteLine(" Malwarebytes is recommended as a free and effective antivirus solution"); writer.WriteLine("\n3. Run Microsoft Safety Scanner as a secondary check"); writer.WriteLine(" Download: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); writer.WriteLine("\n4. Update all your software from official sources"); writer.WriteLine("\n5. Change passwords for important accounts (from a clean device if possible)"); writer.WriteLine("\n6. Monitor your accounts for any suspicious activity"); writer.WriteLine("\nDETAILED MALWARE REMOVAL GUIDES:"); writer.WriteLine("- Malwarebytes Guide: https://www.malwarebytes.com/cybersecurity/basics/how-to-remove-virus-from-computer"); writer.WriteLine("- Microsoft Safety Scanner: https://learn.microsoft.com/en-us/defender-endpoint/safety-scanner-download"); writer.WriteLine("\n============================================="); } } public class ServiceFactory { private readonly Instance _logger; private readonly IScanLogger _scanLogger; private readonly IAssemblyResolverProvider _resolverProvider; private readonly ConfigManager _configManager; private readonly ScanConfig _fallbackConfig; public ServiceFactory(Instance logger) { _logger = logger; _scanLogger = new MelonScanLogger(logger); _resolverProvider = new GameAssemblyResolverProvider(); _fallbackConfig = new ScanConfig(); try { _configManager = new ConfigManager(logger); } catch (Exception ex) { _logger.Error("Failed to create ConfigManager: " + ex.Message); _logger.Msg("Using default configuration values"); } } public ConfigManager CreateConfigManager() { return _configManager; } public AssemblyScanner CreateAssemblyScanner() { ScanConfig config = _configManager?.Config ?? _fallbackConfig; return new AssemblyScanner(RuleFactory.CreateDefaultRules(), config, _resolverProvider); } public ModScanner CreateModScanner() { return new ModScanner(CreateAssemblyScanner(), config: _configManager?.Config ?? _fallbackConfig, logger: _logger, configManager: _configManager); } public ModDisabler CreateModDisabler() { ScanConfig config = _configManager?.Config ?? _fallbackConfig; return new ModDisabler(_logger, config); } public PromptGeneratorService CreatePromptGenerator() { return new PromptGeneratorService(_configManager?.Config ?? _fallbackConfig, _logger); } public IlDumpService CreateIlDumpService() { return new IlDumpService(_logger); } public DeveloperReportGenerator CreateDeveloperReportGenerator() { return new DeveloperReportGenerator(_logger); } } } namespace MLVScan.Services { public class ConfigManager { private readonly Instance _logger; private readonly MelonPreferences_Category _category; private readonly MelonPreferences_Entry<bool> _enableAutoScan; private readonly MelonPreferences_Entry<bool> _enableAutoDisable; private readonly MelonPreferences_Entry<string> _minSeverityForDisable; private readonly MelonPreferences_Entry<string[]> _scanDirectories; private readonly MelonPreferences_Entry<int> _suspiciousThreshold; private readonly MelonPreferences_Entry<string[]> _whitelistedHashes; private readonly MelonPreferences_Entry<bool> _dumpFullIlReports; private readonly MelonPreferences_Entry<bool> _developerMode; public ScanConfig Config { get; private set; } public ConfigManager(Instance logger) { _logger = logger ?? throw new ArgumentNullException("logger"); try { _category = MelonPreferences.CreateCategory("MLVScan"); _enableAutoScan = _category.CreateEntry<bool>("EnableAutoScan", true, (string)null, "Whether to scan mods at startup", false, false, (ValueValidator)null, (string)null); _enableAutoDisable = _category.CreateEntry<bool>("EnableAutoDisable", true, (string)null, "Whether to disable suspicious mods", false, false, (ValueValidator)null, (string)null); _minSeverityForDisable = _category.CreateEntry<string>("MinSeverityForDisable", "Medium", (string)null, "Minimum severity level to trigger disabling (Low, Medium, High, Critical)", false, false, (ValueValidator)null, (string)null); _scanDirectories = _category.CreateEntry<string[]>("ScanDirectories", new string[2] { "Mods", "Plugins" }, (string)null, "Directories to scan for mods", false, false, (ValueValidator)null, (string)null); _suspiciousThreshold = _category.CreateEntry<int>("SuspiciousThreshold", 1, (string)null, "How many suspicious findings required before disabling a mod", false, false, (ValueValidator)null, (string)null); _whitelistedHashes = _category.CreateEntry<string[]>("WhitelistedHashes", Array.Empty<string>(), (string)null, "List of mod SHA256 hashes to skip when scanning", false, false, (ValueValidator)null, (string)null); _dumpFullIlReports = _category.CreateEntry<bool>("DumpFullIlReports", false, (string)null, "When enabled, saves full IL dumps for scanned mods next to reports", false, false, (ValueValidator)null, (string)null); _developerMode = _category.CreateEntry<bool>("DeveloperMode", false, (string)null, "Developer mode: Shows remediation guidance to help mod developers fix false positives", false, false, (ValueValidator)null, (string)null); ((MelonEventBase<LemonAction<bool, bool>>)(object)_enableAutoScan.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)_enableAutoDisable.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<string, string>>)(object)_minSeverityForDisable.OnEntryValueChanged).Subscribe((LemonAction<string, string>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<string[], string[]>>)(object)_scanDirectories.OnEntryValueChanged).Subscribe((LemonAction<string[], string[]>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<int, int>>)(object)_suspiciousThreshold.OnEntryValueChanged).Subscribe((LemonAction<int, int>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<string[], string[]>>)(object)_whitelistedHashes.OnEntryValueChanged).Subscribe((LemonAction<string[], string[]>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)_dumpFullIlReports.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)OnConfigChanged, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)_developerMode.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)OnConfigChanged, 0, false); UpdateConfigFromPreferences(); _logger.Msg("Configuration loaded successfully"); } catch (Exception ex) { _logger.Error("Failed to initialize config system: " + ex.Message); _logger.Msg("Using fallback in-memory configuration"); Config = new ScanConfig(); } } private void OnConfigChanged<T>(T oldValue, T newValue) { UpdateConfigFromPreferences(); _logger.Msg("Configuration updated"); } private void UpdateConfigFromPreferences() { Config = new ScanConfig { EnableAutoScan = _enableAutoScan.Value, EnableAutoDisable = _enableAutoDisable.Value, MinSeverityForDisable = ParseSeverity(_minSeverityForDisable.Value), ScanDirectories = _scanDirectories.Value, SuspiciousThreshold = _suspiciousThreshold.Value, WhitelistedHashes = _whitelistedHashes.Value, DumpFullIlReports = _dumpFullIlReports.Value, DeveloperMode = _developerMode.Value }; } public void SaveConfig(ScanConfig newConfig) { try { _enableAutoScan.Value = newConfig.EnableAutoScan; _enableAutoDisable.Value = newConfig.EnableAutoDisable; _minSeverityForDisable.Value = FormatSeverity(newConfig.MinSeverityForDisable); _scanDirectories.Value = newConfig.ScanDirectories; _suspiciousThreshold.Value = newConfig.SuspiciousThreshold; _whitelistedHashes.Value = newConfig.WhitelistedHashes; _dumpFullIlReports.Value = newConfig.DumpFullIlReports; _developerMode.Value = newConfig.DeveloperMode; MelonPreferences.Save(); _logger.Msg("Configuration saved successfully"); } catch (Exception ex) { _logger.Error("Error saving configuration: " + ex.Message); Config = newConfig; } } public string[] GetWhitelistedHashes() { return _whitelistedHashes.Value; } public void SetWhitelistedHashes(string[] hashes) { if (hashes != null) { string[] array = (from h in hashes where !string.IsNullOrWhiteSpace(h) select h.ToLowerInvariant()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray(); _whitelistedHashes.Value = array; MelonPreferences.Save(); UpdateConfigFromPreferences(); _logger.Msg($"Updated whitelist with {array.Length} hash(es)"); } } public bool IsHashWhitelisted(string hash) { if (string.IsNullOrWhiteSpace(hash)) { return false; } return Config.WhitelistedHashes.Contains<string>(hash.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase); } private static Severity ParseSeverity(string severity) { if (string.IsNullOrWhiteSpace(severity)) { return Severity.Medium; } return severity.ToLower() switch { "critical" => Severity.Critical, "high" => Severity.High, "medium" => Severity.Medium, "low" => Severity.Low, _ => Severity.Medium, }; } private static string FormatSeverity(Severity severity) { return severity switch { Severity.Critical => "Critical", Severity.High => "High", Severity.Medium => "Medium", Severity.Low => "Low", _ => "Medium", }; } } public class DeveloperReportGenerator { private readonly Instance _logger; public DeveloperReportGenerator(Instance logger) { _logger = logger ?? throw new ArgumentNullException("logger"); } public void GenerateConsoleReport(string modName, List<ScanFinding> findings) { if (findings == null || findings.Count == 0) { return; } _logger.Msg("======= DEVELOPER SCAN REPORT ======="); _logger.Msg("Mod: " + modName); _logger.Msg("--------------------------------------"); _logger.Msg($"Total findings: {findings.Count}"); _logger.Msg(""); foreach (IGrouping<string, ScanFinding> item in from f in findings where f.RuleId != null group f by f.RuleId into g orderby g.Max((ScanFinding f) => f.Severity) descending select g) { ScanFinding scanFinding = item.First(); int num = item.Count(); _logger.Msg($"[{scanFinding.Severity}] {scanFinding.Description}"); _logger.Msg(" Rule: " + scanFinding.RuleId); _logger.Msg($" Occurrences: {num}"); if (scanFinding.DeveloperGuidance != null) { _logger.Msg(""); _logger.Msg(" Developer Guidance:"); _logger.Msg(" " + WrapText(scanFinding.DeveloperGuidance.Remediation, 2)); if (!string.IsNullOrEmpty(scanFinding.DeveloperGuidance.DocumentationUrl)) { _logger.Msg(" Documentation: " + scanFinding.DeveloperGuidance.DocumentationUrl); } if (scanFinding.DeveloperGuidance.AlternativeApis != null && scanFinding.DeveloperGuidance.AlternativeApis.Length != 0) { _logger.Msg(" Suggested APIs: " + string.Join(", ", scanFinding.DeveloperGuidance.AlternativeApis)); } if (!scanFinding.DeveloperGuidance.IsRemediable) { _logger.Warning(" ⚠ No safe alternative - this pattern should not be used in MelonLoader mods."); } } else { _logger.Msg(" (No developer guidance available for this rule)"); } _logger.Msg(""); _logger.Msg(" Sample locations:"); foreach (ScanFinding item2 in item.Take(3)) { _logger.Msg(" - " + item2.Location); } if (num > 3) { _logger.Msg($" ... and {num - 3} more"); } _logger.Msg(""); _logger.Msg("--------------------------------------"); } _logger.Msg(""); _logger.Msg("For more information, visit: https://discord.gg/UD4K4chKak"); _logger.Msg("====================================="); } public string GenerateFileReport(string modName, string hash, List<ScanFinding> findings) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("======= MLVScan Developer Report ======="); stringBuilder.AppendLine("Mod: " + modName); stringBuilder.AppendLine("SHA256: " + hash); stringBuilder.AppendLine($"Scan Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); stringBuilder.AppendLine($"Total Findings: {findings.Count}"); stringBuilder.AppendLine(""); foreach (IGrouping<string, ScanFinding> item in from f in findings where f.RuleId != null group f by f.RuleId into g orderby g.Max((ScanFinding f) => f.Severity) descending select g) { ScanFinding scanFinding = item.First(); int num = item.Count(); stringBuilder.AppendLine("========================================="); stringBuilder.AppendLine("Rule: " + scanFinding.RuleId); stringBuilder.AppendLine($"Severity: {scanFinding.Severity}"); stringBuilder.AppendLine("Description: " + scanFinding.Description); stringBuilder.AppendLine($"Occurrences: {num}"); stringBuilder.AppendLine(""); if (scanFinding.DeveloperGuidance != null) { stringBuilder.AppendLine("--- DEVELOPER GUIDANCE ---"); stringBuilder.AppendLine("Remediation: " + scanFinding.DeveloperGuidance.Remediation); stringBuilder.AppendLine(""); if (!string.IsNullOrEmpty(scanFinding.DeveloperGuidance.DocumentationUrl)) { stringBuilder.AppendLine("Documentation: " + scanFinding.DeveloperGuidance.DocumentationUrl); } if (scanFinding.DeveloperGuidance.AlternativeApis != null && scanFinding.DeveloperGuidance.AlternativeApis.Length != 0) { stringBuilder.AppendLine("Suggested APIs:"); string[] alternativeApis = scanFinding.DeveloperGuidance.AlternativeApis; foreach (string text in alternativeApis) { stringBuilder.AppendLine(" - " + text); } } if (!scanFinding.DeveloperGuidance.IsRemediable) { stringBuilder.AppendLine(""); stringBuilder.AppendLine("WARNING: This pattern has no safe alternative and should not be used in MelonLoader mods."); } stringBuilder.AppendLine(""); } else { stringBuilder.AppendLine("(No developer guidance available for this rule)"); stringBuilder.AppendLine(""); } stringBuilder.AppendLine("--- FINDINGS ---"); foreach (ScanFinding item2 in item) { stringBuilder.AppendLine("Location: " + item2.Location); if (!string.IsNullOrEmpty(item2.CodeSnippet)) { stringBuilder.AppendLine("Code Snippet:"); stringBuilder.AppendLine(item2.CodeSnippet); } stringBuilder.AppendLine(""); } } stringBuilder.AppendLine("========================================="); stringBuilder.AppendLine(""); stringBuilder.AppendLine("Need help? Join the community: https://discord.gg/UD4K4chKak"); return stringBuilder.ToString(); } private string WrapText(string text, int indent) { string text2 = new string(' ', indent); int num = 80 - indent; string[] array = text.Split(' '); List<string> list = new List<string>(); string text3 = ""; string[] array2 = array; foreach (string text4 in array2) { if (text3.Length + text4.Length + 1 > num) { if (!string.IsNullOrEmpty(text3)) { list.Add(text3); } text3 = text4; } else { text3 = text3 + ((text3.Length > 0) ? " " : "") + text4; } } if (!string.IsNullOrEmpty(text3)) { list.Add(text3); } return string.Join("\n " + text2, list); } } public class IlDumpService { private readonly Instance _logger; private readonly DefaultAssemblyResolver _assemblyResolver; public IlDumpService(Instance logger) { _logger = logger; _assemblyResolver = BuildResolver(); } public bool TryDumpAssembly(string assemblyPath, string outputPath) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(assemblyPath) || string.IsNullOrWhiteSpace(outputPath)) { return false; } try { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false, AssemblyResolver = (IAssemblyResolver)(object)_assemblyResolver }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(assemblyPath, val); using StreamWriter streamWriter = new StreamWriter(outputPath); streamWriter.WriteLine("; Full IL dump for " + Path.GetFileName(assemblyPath)); streamWriter.WriteLine($"; Generated: {DateTime.Now}"); streamWriter.WriteLine(); Enumerator<ModuleDefinition> enumerator = val2.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { ModuleDefinition current = enumerator.Current; streamWriter.WriteLine(".module " + ((ModuleReference)current).Name); streamWriter.WriteLine(); Enumerator<TypeDefinition> enumerator2 = current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { WriteType(enumerator2.Current, streamWriter); } } finally { ((IDisposable)enumerator2).Dispose(); } } } finally { ((IDisposable)enumerator).Dispose(); } Instance logger = _logger; if (logger != null) { logger.Msg("Saved IL dump to: " + outputPath); } return true; } catch (Exception ex) { Instance logger2 = _logger; if (logger2 != null) { logger2.Error("Failed to dump IL for " + Path.GetFileName(assemblyPath) + ": " + ex.Message); } return false; } } private DefaultAssemblyResolver BuildResolver() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown DefaultAssemblyResolver val = new DefaultAssemblyResolver(); string gameRootDirectory = MelonEnvironment.GameRootDirectory; string text = Path.Combine(gameRootDirectory, "MelonLoader"); ((BaseAssemblyResolver)val).AddSearchDirectory(gameRootDirectory); if (Directory.Exists(text)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text); string text2 = Path.Combine(text, "Managed"); if (Directory.Exists(text2)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text2); } string text3 = Path.Combine(text, "Dependencies"); if (Directory.Exists(text3)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text3); string[] directories = Directory.GetDirectories(text3, "*", SearchOption.AllDirectories); foreach (string text4 in directories) { ((BaseAssemblyResolver)val).AddSearchDirectory(text4); } } } string text5 = Path.Combine(gameRootDirectory, "Schedule I_Data", "Managed"); if (Directory.Exists(text5)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text5); } return val; } private static void WriteType(TypeDefinition type, StreamWriter writer) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) writer.WriteLine(".class " + ((MemberReference)type).FullName); Enumerator<MethodDefinition> enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { WriteMethod(enumerator.Current, writer); } } finally { ((IDisposable)enumerator).Dispose(); } Enumerator<TypeDefinition> enumerator2 = type.NestedTypes.GetEnumerator(); try { while (enumerator2.MoveNext()) { WriteType(enumerator2.Current, writer); } } finally { ((IDisposable)enumerator2).Dispose(); } } private static void WriteMethod(MethodDefinition method, StreamWriter writer) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) try { string text = string.Join(", ", ((IEnumerable<ParameterDefinition>)((MethodReference)method).Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName + " " + ((ParameterReference)p).Name)); writer.WriteLine(" .method " + ((MemberReference)((MethodReference)method).ReturnType).FullName + " " + ((MemberReference)method).Name + "(" + text + ")"); if (!method.HasBody) { writer.WriteLine(" // No body (abstract / external)"); writer.WriteLine(); return; } writer.WriteLine(" {"); Enumerator<Instruction> enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; string text2 = FormatOperand(current.Operand); string text3 = $" IL_{current.Offset:X4}: {current.OpCode}"; if (!string.IsNullOrEmpty(text2)) { text3 = text3 + " " + text2; } writer.WriteLine(text3); } } finally { ((IDisposable)enumerator).Dispose(); } writer.WriteLine(" }"); writer.WriteLine(); } catch (Exception ex) { writer.WriteLine(" // Failed to dump method " + ((MemberReference)method).Name + ": " + ex.Message); writer.WriteLine(); } } private static string FormatOperand(object operand) { if (operand != null) { if (!(operand is string text)) { MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val == null) { FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null); if (val2 == null) { TypeReference val3 = (TypeReference)((operand is TypeReference) ? operand : null); if (val3 == null) { Instruction val4 = (Instruction)((operand is Instruction) ? operand : null); if (val4 == null) { if (operand is Instruction[] source) { return string.Join(", ", source.Select((Instruction t) => $"IL_{t.Offset:X4}")); } return operand.ToString() ?? string.Empty; } return $"IL_{val4.Offset:X4}"; } return ((MemberReference)val3).FullName; } return ((MemberReference)val2).FullName; } return ((MemberReference)val).FullName; } return "\"" + text + "\""; } return string.Empty; } } public class ModDisabler { private readonly Instance _logger = logger ?? throw new ArgumentNullException("logger"); private readonly ScanConfig _config = config ?? throw new ArgumentNullException("config"); private const string DisabledExtension = ".di"; public ModDisabler(Instance logger, ScanConfig config) { } public List<DisabledModInfo> DisableSuspiciousMods(Dictionary<string, List<ScanFinding>> scanResults, bool forceDisable = false) { if (!forceDisable && !_config.EnableAutoDisable) { _logger.Msg("Automatic disabling is turned off in configuration"); return new List<DisabledModInfo>(); } List<DisabledModInfo> list = new List<DisabledModInfo>(); foreach (KeyValuePair<string, List<ScanFinding>> scanResult in scanResults) { scanResult.Deconstruct(out var key, out var value); string text = key; List<ScanFinding> list2 = value.Where((ScanFinding f) => f.Severity >= _config.MinSeverityForDisable).ToList(); if (list2.Count == 0) { _logger.Msg($"Mod {Path.GetFileName(text)} has findings but none meet minimum severity threshold ({_config.MinSeverityForDisable} - If this is set to Medium, the mod is likely not malicious)."); continue; } if (!forceDisable && list2.Count < _config.SuspiciousThreshold) { _logger.Msg("Mod " + Path.GetFileName(text) + " has suspicious patterns but below threshold"); continue; } try { string fileHash = ModScanner.CalculateFileHash(text); string text2 = Path.ChangeExtension(text, ".di"); if (File.Exists(text2)) { File.Delete(text2); } File.Move(text, text2); _logger.Warning("Disabled potentially malicious mod: " + Path.GetFileName(text)); list.Add(new DisabledModInfo(text, text2, fileHash)); } catch (Exception ex) { _logger.Error("Failed to disable mod " + Path.GetFileName(text) + ": " + ex.Message); } } return list; } } public class ModScanner { private readonly AssemblyScanner _assemblyScanner = assemblyScanner ?? throw new ArgumentNullException("assemblyScanner"); private readonly Instance _logger = logger ?? throw new ArgumentNullException("logger"); private readonly ScanConfig _config = config ?? throw new ArgumentNullException("config"); private readonly ConfigManager _configManager = configManager ?? throw new ArgumentNullException("configManager"); public ModScanner(AssemblyScanner assemblyScanner, Instance logger, ScanConfig config, ConfigManager configManager) { } public Dictionary<string, List<ScanFinding>> ScanAllMods(bool forceScanning = false) { Dictionary<string, List<ScanFinding>> dictionary = new Dictionary<string, List<ScanFinding>>(); if (!forceScanning && !_config.EnableAutoScan) { _logger.Msg("Automatic scanning is disabled in configuration"); return dictionary; } string[] scanDirectories = _config.ScanDirectories; foreach (string path in scanDirectories) { string text = Path.Combine(MelonEnvironment.GameRootDirectory, path); if (!Directory.Exists(text)) { _logger.Warning("Directory not found: " + text); } else { ScanDirectory(text, dictionary); } } ScanThunderstoreModManager(dictionary); return dictionary; } private void ScanDirectory(string directoryPath, Dictionary<string, List<ScanFinding>> results) { string[] files = Directory.GetFiles(directoryPath, "*.dll", SearchOption.AllDirectories); _logger.Msg($"Found {files.Length} potential mod files in {directoryPath}"); string[] array = files; foreach (string text in array) { try { string fileName = Path.GetFileName(text); string text2 = CalculateFileHash(text); if (Path.GetFullPath(text).Equals(Path.GetFullPath(typeof(Core).Assembly.Location), StringComparison.OrdinalIgnoreCase)) { _logger.Msg("Skipping self: " + fileName); } else if (_configManager.IsHashWhitelisted(text2)) { _logger.Msg("Skipping whitelisted mod: " + fileName + " [Hash: " + text2 + "]"); } else { List<ScanFinding> list = _assemblyScanner.Scan(text).ToList(); if (list.Count >= _config.SuspiciousThreshold) { results.Add(text, list); _logger.Warning($"Found {list.Count} suspicious patterns in {Path.GetFileName(text)}"); } } } catch (Exception ex) { _logger.Error("Error scanning " + Path.GetFileName(text) + ": " + ex.Message); } } } private void ScanThunderstoreModManager(Dictionary<string, List<ScanFinding>> results) { try { string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Thunderstore Mod Manager", "DataFolder"); if (!Directory.Exists(path)) { return; } string[] directories = Directory.GetDirectories(path); for (int i = 0; i < directories.Length; i++) { string path2 = Path.Combine(directories[i], "profiles"); if (!Directory.Exists(path2)) { continue; } string[] directories2 = Directory.GetDirectories(path2); foreach (string path3 in directories2) { string text = Path.Combine(path3, "Mods"); if (Directory.Exists(text)) { _logger.Msg("Scanning Thunderstore profile mods: " + text); ScanDirectory(text, results); } string text2 = Path.Combine(path3, "Plugins"); if (Directory.Exists(text2)) { _logger.Msg("Scanning Thunderstore profile plugins: " + text2); ScanDirectory(text2, results); } } } } catch (Exception ex) { _logger.Error("Error scanning Thunderstore Mod Manager directories: " + ex.Message); } } public static string CalculateFileHash(string filePath) { try { if (!File.Exists(filePath)) { return "File not found: " + filePath; } using SHA256 sHA = SHA256.Create(); using FileStream inputStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant(); } catch (UnauthorizedAccessException) { return "Access denied"; } catch (IOException ex2) { return "IO Error: " + ex2.Message; } catch (Exception ex3) { return "Error: " + ex3.Message; } } } public class PromptGeneratorService { private readonly ScanConfig _config; private readonly Instance _logger; public PromptGeneratorService(ScanConfig config, Instance logger) { _config = config ?? throw new ArgumentNullException("config"); _logger = logger ?? throw new ArgumentNullException("logger"); } public string GeneratePrompt(string modPath, List<ScanFinding> findings) { if (findings == null || !findings.Any()) { return "No suspicious findings to analyze."; } string fileName = Path.GetFileName(modPath); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Mod Security Analysis Request"); stringBuilder.AppendLine(); stringBuilder.AppendLine("I need to determine if the following mod is malicious or a false positive. Below is a security scan report generated by MLVScan (a security tool for MelonLoader mods)."); stringBuilder.AppendLine(); stringBuilder.AppendLine("## Mod Information"); stringBuilder.AppendLine("- **Filename**: " + fileName); stringBuilder.AppendLine($"- **Scan Date**: {DateTime.Now}"); stringBuilder.AppendLine($"- **Total Suspicious Patterns**: {findings.Count}"); stringBuilder.AppendLine(); Dictionary<Severity, int> dictionary = (from f in findings group f by f.Severity into g orderby (int)g.Key descending select g).ToDictionary((IGrouping<Severity, ScanFinding> g) => g.Key, (IGrouping<Severity, ScanFinding> g) => g.Count()); stringBuilder.AppendLine("## Severity Breakdown"); foreach (KeyValuePair<Severity, int> item in dictionary) { stringBuilder.AppendLine($"- **{FormatSeverityLabel(item.Key)}**: {item.Value} issue(s)"); } stringBuilder.AppendLine(); stringBuilder.AppendLine("## Detailed Findings"); Dictionary<string, List<ScanFinding>> dictionary2 = (from f in findings group f by f.Description).ToDictionary((IGrouping<string, ScanFinding> g) => g.Key, (IGrouping<string, ScanFinding> g) => g.ToList()); var (dictionary5, dictionary6) = ExtractCodeBlocks(modPath, findings); foreach (KeyValuePair<string, List<ScanFinding>> item2 in dictionary2) { stringBuilder.AppendLine("### " + item2.Key); stringBuilder.AppendLine($"- **Severity**: {item2.Value[0].Severity}"); stringBuilder.AppendLine($"- **Occurrences**: {item2.Value.Count}"); stringBuilder.AppendLine("- **Locations & Snippets**:"); foreach (ScanFinding item3 in item2.Value.Take(5)) { stringBuilder.AppendLine(" - **Location**: " + item3.Location); if (!string.IsNullOrEmpty(item3.CodeSnippet)) { stringBuilder.AppendLine(" **IL Snippet (Exact location of suspicious call)**:"); stringBuilder.AppendLine(" ```"); string[] array = item3.CodeSnippet.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { stringBuilder.AppendLine(" " + text); } stringBuilder.AppendLine(" ```"); } if (dictionary5.TryGetValue(item3.Location, out var value) && !string.IsNullOrWhiteSpace(value)) { stringBuilder.AppendLine(" **Attempted C# Decompilation (Entire Method Context & Type Info)**:"); stringBuilder.AppendLine(" ```csharp"); string[] array = value.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { stringBuilder.AppendLine(" " + text2); } stringBuilder.AppendLine(" ```"); } if (dictionary6.TryGetValue(item3.Location, out var value2) && !string.IsNullOrWhiteSpace(value2)) { stringBuilder.AppendLine(" **Surrounding Class Structure (Member Signatures Only)**:"); stringBuilder.AppendLine(" ```csharp"); string[] array = value2.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text3 in array) { stringBuilder.AppendLine(" " + text3); } stringBuilder.AppendLine(" ```"); } stringBuilder.AppendLine(); } if (item2.Value.Count > 5) { stringBuilder.AppendLine($" - *And {item2.Value.Count - 5} more occurrences (details omitted for brevity)*"); } stringBuilder.AppendLine(); } string value3 = ExtractAssemblyMetadata(modPath); if (!string.IsNullOrEmpty(value3)) { stringBuilder.AppendLine("## Assembly Metadata"); stringBuilder.AppendLine(value3); stringBuilder.AppendLine(); } stringBuilder.AppendLine("## Request"); stringBuilder.AppendLine("Based on this scan report and code analysis, please help me determine:"); stringBuilder.AppendLine("1. Is this mod likely to be malicious or is it a false positive?"); stringBuilder.AppendLine("2. If potentially malicious, what specific security risks does it pose?"); stringBuilder.AppendLine("3. What is the intent of the suspicious code? Is there a benign explanation?"); stringBuilder.AppendLine("4. What further actions should I take? (Whitelist it, delete it, report it, etc.)"); stringBuilder.AppendLine(); stringBuilder.AppendLine("Please explain your reasoning with reference to the specific code patterns and provide context about:"); stringBuilder.AppendLine("- Whether these patterns are common in legitimate mods or game utilities"); stringBuilder.AppendLine("- Alternative explanations for the suspicious patterns"); stringBuilder.AppendLine("- Your confidence level in the assessment"); stringBuilder.AppendLine(); stringBuilder.AppendLine("Important context: This is a mod for a game using MelonLoader (a mod loading framework). Legitimate mods generally don't need to use system-level APIs like shell execution, registry access, etc."); return stringBuilder.ToString(); } private Tuple<Dictionary<string, string>, Dictionary<string, string>> ExtractCodeBlocks(string modPath, List<ScanFinding> findings) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, string> dictionary = new Dictionary<string, string>(); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); try { if (!File.Exists(modPath)) { return Tuple.Create(dictionary, dictionary2); } ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(modPath, val); try { Dictionary<string, List<string>> dictionary3 = new Dictionary<string, List<string>>(); Enumerator<ModuleDefinition> enumerator = val2.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { Enumerator<TypeDefinition> enumerator2 = enumerator.Current.Types.GetEnumerator(); try { while (enumerator2.MoveNext()) { TypeDefinition current = enumerator2.Current; CollectSuspiciousStrings(current, dictionary3); } } finally { ((IDisposable)enumerator2).Dispose(); } } } finally { ((IDisposable)enumerator).Dispose(); } if (dictionary3.Any()) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("// Notable string literals found in the assembly:"); stringBuilder.AppendLine(); foreach (string item in dictionary3.Keys.OrderBy((string k) => k)) { stringBuilder.AppendLine("// Potential " + item + ":"); foreach (string item2 in dictionary3[item].Take(10)) { stringBuilder.AppendLine("// \"" + EscapeStringForCode(item2) + "\""); } if (dictionary3[item].Count > 10) { stringBuilder.AppendLine($"// ... and {dictionary3[item].Count - 10} more"); } stringBuilder.AppendLine(); } dictionary["SuspiciousStrings"] = stringBuilder.ToString(); } foreach (ScanFinding finding in findings) { try { string location = finding.Location; if (location == "Assembly scanning" || !location.Contains(".")) { continue; } _ = string.Empty; string fullTypeName; string methodNameFromFinding; if (location.Contains(":")) { string[] array = location.Split(':'); string text = array[0]; _ = array[1]; int num = text.LastIndexOf('.'); if (num > 0) { fullTypeName = text.Substring(0, num); methodNameFromFinding = text.Substring(num + 1); goto IL_02d1; } } else { int num2 = location.LastIndexOf('.'); if (num2 > 0) { fullTypeName = location.Substring(0, num2); methodNameFromFinding = location.Substring(num2 + 1); goto IL_02d1; } } goto end_IL_0212; IL_02d1: TypeDefinition val3 = FindType(val2.MainModule, fullTypeName); if (val3 == null) { continue; } if (!dictionary2.ContainsKey(finding.Location) || dictionary2[finding.Location] == string.Empty) { dictionary2[finding.Location] = GenerateClassStructure(val3, methodNameFromFinding); } MethodDefinition val4 = ((IEnumerable<MethodDefinition>)val3.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == methodNameFromFinding)); if (val4 == null) { if (!dictionary.ContainsKey(finding.Location)) { dictionary[finding.Location] = "// DllImport or external method: " + finding.Location + ". Primary context is the IL snippet and class structure."; } continue; } if (!val4.HasBody && !val4.IsAbstract) { if (!dictionary.ContainsKey(finding.Location)) { dictionary[finding.Location] = "// Method " + methodNameFromFinding + " has no body or is abstract."; } continue; } string text2 = DecompileMethod(val4); if (string.IsNullOrEmpty(text2)) { continue; } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.AppendLine("// Context: Method is part of " + ((MemberReference)val3).FullName); if (val3.HasCustomAttributes) { stringBuilder2.AppendLine("// Type attributes:"); foreach (CustomAttribute item3 in ((IEnumerable<CustomAttribute>)val3.CustomAttributes).Take(5)) { stringBuilder2.AppendLine("// - " + ((MemberReference)item3.AttributeType).Name); } if (val3.CustomAttributes.Count > 5) { stringBuilder2.AppendLine($"// - ...and {val3.CustomAttributes.Count - 5} more"); } } if (val3.BaseType != null && ((MemberReference)val3.BaseType).FullName != "System.Object") { stringBuilder2.AppendLine("// Inherits from: " + ((MemberReference)val3.BaseType).FullName); } List<MethodDefinition> list = FindRelatedSuspiciousMethods(val3, val4); if (list.Any()) { stringBuilder2.AppendLine("// Other suspicious methods in this class (names only):"); foreach (MethodDefinition item4 in list.Take(3)) { stringBuilder2.AppendLine("// - " + ((MemberReference)item4).Name); } if (list.Count > 3) { stringBuilder2.AppendLine($"// - ...and {list.Count - 3} more"); } } stringBuilder2.AppendLine(); stringBuilder2.AppendLine("// Finding Description: " + finding.Description); stringBuilder2.AppendLine($"// Severity: {finding.Severity}"); stringBuilder2.AppendLine(); dictionary[finding.Location] = stringBuilder2.ToString() + text2; end_IL_0212:; } catch (Exception ex) { _logger.Error("Failed to extract code for " + finding.Location + ": " + ex.Message); dictionary[finding.Location] = "// Error extracting detailed code for " + finding.Location + ": " + ex.Message; } } if (((IEnumerable<Resource>)val2.MainModule.Resources).Any()) { StringBuilder stringBuilder3 = new StringBuilder(); stringBuilder3.AppendLine("// Assembly Resources (could contain hidden payloads):"); foreach (Resource item5 in ((IEnumerable<Resource>)val2.MainModule.Resources).Take(20)) { stringBuilder3.AppendLine("// " + item5.Name + " - " + GetResourceTypeName(item5)); } if (val2.MainModule.Resources.Count > 20) { stringBuilder3.AppendLine($"// ...and {val2.MainModule.Resources.Count - 20} more resources"); } dictionary["Assembly.Resources"] = stringBuilder3.ToString(); } } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception ex2) { _logger.Error("Failed to extract code blocks from " + modPath + ": " + ex2.Message); } return Tuple.Create(dictionary, dictionary2); } private void CollectSuspiciousStrings(TypeDefinition type, Dictionary<string, List<string>> suspiciousStrings) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) Enumerator<MethodDefinition> enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; if (!current.HasBody) { continue; } Enumerator<Instruction> enumerator2 = current.Body.Instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; OpCode opCode = current2.OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && current2.Operand is string text && IsSuspiciousString(text, out var category)) { if (!suspiciousStrings.ContainsKey(category)) { suspiciousStrings[category] = new List<string>(); } if (!suspiciousStrings[category].Contains(text)) { suspiciousStrings[category].Add(text); } } } } finally { ((IDisposable)enumerator2).Dispose(); } } } finally { ((IDisposable)enumerator).Dispose(); } Enumerator<TypeDefinition> enumerator3 = type.NestedTypes.GetEnumerator(); try { while (enumerator3.MoveNext()) { TypeDefinition current3 = enumerator3.Current; CollectSuspiciousStrings(current3, suspiciousStrings); } } finally { ((IDisposable)enumerator3).Dispose(); } } private bool IsSuspiciousString(string value, out string category) { if (string.IsNullOrEmpty(value)) { category = string.Empty; return false; } if (value.Length > 20 && IsLikelyBase64(value)) { category = "Base64 encoded data"; return true; } if (value.StartsWith("http://") || value.StartsWith("https://") || value.StartsWith("ftp://") || value.StartsWith("ws://")) { category = "URL"; return true; } if (value.Contains(".exe") || value.Contains(".dll") || value.Contains(".bat") || value.Contains(".cmd") || value.Contains(".ps1") || value.Contains(".vbs")) { category = "executable file reference"; return true; } if (IsLikelyIPAddress(value)) { category = "IP address"; return true; } if (value.StartsWith("HKEY_") || value.Contains("\\Software\\") || value.Contains("\\Microsoft\\") || value.Contains("\\System\\")) { category = "registry path"; return true; } if (value.StartsWith("cmd ") || value.StartsWith("powershell ") || value.Contains(" /c ") || value.Contains(" /k ")) { category = "command line"; return true; } if (value.Contains("encrypt") || value.Contains("decrypt") || value.Contains("aes") || value.Contains("rsa") || value.Contains("md5") || value.Contains("sha") || value.Contains("hash")) { category = "cryptographic reference"; return true; } if ((value.Contains(".") && !value.Contains(" ") && !value.Contains("\\") && !value.EndsWith(".cs") && !value.EndsWith(".txt") && value.Length > 5) || value.Contains("pastebin") || value.Contains("discord") || value.Contains("webhook")) { category = "domain or web service"; return true; } category = string.Empty; return false; } private bool IsLikelyBase64(string value) { if (value.Length % 4 != 0) { return false; } foreach (char c2 in value) { if ((c2 < 'A' || c2 > 'Z') && (c2 < 'a' || c2 > 'z') && (c2 < '0' || c2 > '9') && c2 != '+' && c2 != '/' && c2 != '=') { return false; } } if (value.Length > 20 && value.Any((char c) => c >= 'A' && c <= 'Z') && value.Any((char c) => c >= 'a' && c <= 'z')) { return value.Any((char c) => c >= '0' && c <= '9'); } return false; } private bool IsLikelyIPAddress(string value) { string pattern = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"; string pattern2 = "^[0-9a-fA-F:]+"; if (!Regex.IsMatch(value, pattern)) { return Regex.IsMatch(value, pattern2); } return true; } private string GetResourceTypeName(Resource resource) { //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) EmbeddedResource val = (EmbeddedResource)(object)((resource is EmbeddedResource) ? resource : null); if (val != null) { using (Stream stream = val.GetResourceStream()) { if (stream.Length > 0) { byte[] array = new byte[Math.Min(stream.Length, 16L)]; stream.Read(array, 0, array.Length); if (array.Length >= 2 && array[0] == 77 && array[1] == 90) { return "PE File/DLL (MZ header)"; } if (array.Length >= 4 && array[0] == 127 && array[1] == 69 && array[2] == 76 && array[3] == 70) { return "ELF Binary"; } if (array.Length >= 4 && array[0] == 80 && array[1] == 75 && array[2] == 3 && array[3] == 4) { return "ZIP Archive"; } if (array.Length >= 2 && array[0] == byte.MaxValue && array[1] == 216) { return "JPEG Image"; } if (array.Length >= 3 && array[0] == 71 && array[1] == 73 && array[2] == 70) { return "GIF Image"; } if (array.Length >= 4 && ((array[0] == 137 && array[1] == 80 && array[2] == 78 && array[3] == 71) || (array[0] == 66 && array[1] == 77))) { return "PNG or BMP Image"; } return $"Binary data ({stream.Length} bytes)"; } return "Empty resource"; } } ResourceType resourceType = resource.ResourceType; return ((object)(ResourceType)(ref resourceType)).ToString(); } private List<MethodDefinition> FindRelatedSuspiciousMethods(TypeDefinition type, MethodDefinition currentMethod) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) List<MethodDefinition> list = new List<MethodDefinition>(); Enumerator<MethodDefinition> enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; if (current == currentMethod || !current.HasBody) { continue; } Enumerator<Instruction> enumerator2 = current.Body.Instructions.GetEnumerator(); try { while (enumerator2.MoveNext()) { Instruction current2 = enumerator2.Current; OpCode opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = current2.OpCode; if (!(((OpCode)(ref opCode)).Name == "newobj")) { continue; } } } object operand = current2.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && IsSuspiciousMethodCall(val)) { list.Add(current); break; } } } finally { ((IDisposable)enumerator2).Dispose(); } } return list; } finally { ((IDisposable)enumerator).Dispose(); } } private TypeDefinition FindType(ModuleDefinition module, string fullTypeName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Enumerator<TypeDefinition> enumerator = module.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; if (((MemberReference)current).FullName == fullTypeName) { return current; } TypeDefinition val = FindNestedType(current, fullTypeName); if (val != null) { return val; } } } finally { ((IDisposable)enumerator).Dispose(); } return null; } private TypeDefinition FindNestedType(TypeDefinition parentType, string fullTypeName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Enumerator<TypeDefinition> enumerator = parentType.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; if (((MemberReference)current).FullName == fullTypeName) { return current; } TypeDefinition val = FindNestedType(current, fullTypeName); if (val != null) { return val; } } } finally { ((IDisposable)enumerator).Dispose(); } return null; } private string DecompileMethod(MethodDefinition method) { //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) if (!method.HasBody) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(GetMethodVisibility(method) + " " + ((MemberReference)((MethodReference)method).ReturnType).Name + " " + ((MemberReference)method).Name + "(" + GetMethodParameters(method) + ")"); stringBuilder.AppendLine("{"); string value = ReconstructCode(method); if (!string.IsNullOrEmpty(value)) { stringBuilder.AppendLine(value); } else { stringBuilder.AppendLine(" // Method body:"); List<ExceptionHandler> source = ((IEnumerable<ExceptionHandler>)method.Body.ExceptionHandlers).ToList(); HashSet<int> hashSet = source.Select((ExceptionHandler h) => h.HandlerStart.Offset).ToHashSet(); HashSet<int> hashSet2 = source.Select(delegate(ExceptionHandler h) { Instruction handlerEnd = h.HandlerEnd; return (handlerEnd == null) ? int.MaxValue : handlerEnd.Offset; }).ToHashSet(); HashSet<int> hashSet3 = source.Select((ExceptionHandler h) => h.TryStart.Offset).ToHashSet(); HashSet<int> hashSet4 = source.Select(delegate(ExceptionHandler h) { Instruction tryEnd = h.TryEnd; return (tryEnd == null) ? int.MaxValue : tryEnd.Offset; }).ToHashSet(); bool flag = false; bool flag2 = false; List<VariableDefinition> variables = ((IEnumerable<VariableDefinition>)method.Body.Variables).ToList(); List<ParameterDefinition> parameters = ((IEnumerable<ParameterDefinition>)((MethodReference)method).Parameters).ToList(); List<Instruction> list = ((IEnumerable<Instruction>)method.Body.Instructions).ToList(); for (int i = 0; i < list.Count; i++) { Instruction instruction = list[i]; if (hashSet3.Contains(instruction.Offset) && !flag) { stringBuilder.AppendLine(" try {"); flag = true; } if (hashSet.Contains(instruction.Offset) && !flag2) { ExceptionHandler val = ((IEnumerable<ExceptionHandler>)source).FirstOrDefault((Func<ExceptionHandler, bool>)((ExceptionHandler h) => h.HandlerStart.Offset == instruction.Offset)); object obj; if (val == null) { obj = null; } else { TypeReference catchType = val.CatchType; obj = ((catchType != null) ? ((MemberReference)catchType).Name : null); } if (obj == null) { obj = "Exception"; } stringBuilder.AppendLine(" } catch (" + (string?)obj + ") {"); flag = false; flag2 = true; } if ((hashSet4.Contains(instruction.Offset) && flag) || (hashSet2.Contains(instruction.Offset) && flag2)) { stringBuilder.AppendLine(" }"); flag = false; flag2 = false; } string text = FormatInstructionWithContext(instruction, i, list, variables, parameters); if (!string.IsNullOrEmpty(text)) { string text2 = " "; if (flag || flag2) { text2 += " "; } stringBuilder.AppendLine(text2 + text); } } if (flag || flag2) { stringBuilder.AppendLine(" }"); } } if (((MethodReference)method).Parameters.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Method parameters:"); Enumerator<ParameterDefinition> enumerator = ((MethodReference)method).Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; stringBuilder.AppendLine(" // " + ((MemberReference)((ParameterReference)current).ParameterType).FullName + " " + ((ParameterReference)current).Name); } } finally { ((IDisposable)enumerator).Dispose(); } } if (method.Body.Variables.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Local variables:"); Enumerator<VariableDefinition> enumerator2 = method.Body.Variables.GetEnumerator(); try { while (enumerator2.MoveNext()) { VariableDefinition current2 = enumerator2.Current; stringBuilder.AppendLine($" // {((MemberReference)((VariableReference)current2).VariableType).FullName} var_{((VariableReference)current2).Index}"); } } finally { ((IDisposable)enumerator2).Dispose(); } } List<string> list2 = FindSuspiciousApiCalls(method); if (list2.Any()) { stringBuilder.AppendLine(); stringBuilder.AppendLine(" // Suspicious API calls:"); foreach (string item in list2) { stringBuilder.AppendLine(" // " + item); } } stringBuilder.AppendLine("}"); return stringBuilder.ToString(); } private string ReconstructCode(MethodDefinition method) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); try { List<Instruction> list = ((IEnumerable<Instruction>)method.Body.Instructions).ToList(); for (int i = 0; i < list.Count; i++) { Instruction val = list[i]; OpCode opCode = val.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = val.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = val.OpCode; if (((OpCode)(ref opCode)).Name == "newobj") { object operand = val.Operand; MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null); if (val2 != null) { stringBuilder.AppendLine(" new " + ((MemberReference)((MemberReference)val2).DeclaringType).Name + "(...);"); continue; } } opCode = val.OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && val.Operand is string value) { stringBuilder.AppendLine(" // String: \"" + EscapeStringForCode(value) + "\""); } continue; } } object operand2 = val.Operand; MethodReference val3 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val3 != null) { string methodCallRepresentation = GetMethodCallRepresentation(val3, list, i); if (!string.IsNullOrEmpty(methodCallRepresentation)) { stringBuilder.AppendLine(" " + methodCallRepresentation + ";"); } } } if (stringBuilder.Length == 0) { return string.Empty; } return stringBuilder.ToString(); } catch { return string.Empty; } } private string EscapeStringForCode(string value) { if (string.IsNullOrEmpty(value)) { return value; } return value.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n") .Replace("\t", "\\t"); } private string GetMethodCallRepresentation(MethodReference methodRef, List<Instruction> instructions, int currentIndex) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) string name = ((MemberReference)methodRef).Name; string name2 = ((MemberReference)((MemberReference)methodRef).DeclaringType).Name; if (name.Contains("Process") && name.Contains("Start")) { return "System.Diagnostics.Process.Start(...) // Executes external process"; } if (name.Contains("Load") && name2.Contains("Assembly")) { return "Assembly." + name + "(...) // Dynamically loads code"; } if ((name.Contains("FromBase64") || name.Contains("GetString")) && name2.Contains("Convert")) { string text = "..."; for (int num = currentIndex - 1; num >= Math.Max(0, currentIndex - 5); num--) { OpCode opCode = instructions[num].OpCode; if (((OpCode)(ref opCode)).Name == "ldstr" && instructions[num].Operand is string) { text = instructions[num].Operand.ToString(); if (text.Length > 20) { text = text.Substring(0, 17) + "..."; } break; } } return "Convert." + name + "(\"" + text + "\") // Decodes Base64 data"; } if (name.Contains("RegOpenKey") || name.Contains("RegCreateKey") || (name.Contains("Registry") && (name.Contains("Get") || name.Contains("Set")))) { return name2 + "." + name + "(...) // Registry manipulation"; } if (name.Contains("CreateFile") || name.Contains("WriteFile") || name.Contains("ReadFile")) { return name2 + "." + name + "(...) // File system operation"; } if (name.Contains("Socket") || name.Contains("Connect") || name.Contains("Send") || name.Contains("Receive")) { return name2 + "." + name + "(...) // Network communication"; } return name2 + "." + name + "(...)"; } private string FormatInstructionWithContext(Instruction instruction, int index, List<Instruction> allInstructions, List<VariableDefinition> variables, List<ParameterDefinition> parameters) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instruction.OpCode; string name = ((OpCode)(ref opCode)).Name; if (name == "nop") { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"/* {instruction.Offset:X4} */ "); object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { stringBuilder.Append(name + " " + ((MemberReference)((MemberReference)val).DeclaringType).FullName + "." + ((MemberReference)val).Name + "()"); if (val.Parameters.Count > 0) { stringBuilder.Append(" // Takes: " + string.Join(", ", ((IEnumerable<ParameterDefinition>)val.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).Name))); } if (IsSuspiciousMethodCall(val)) { stringBuilder.Append(" // SUSPICIOUS: " + GetSuspiciousMethodDescription(val)); } } else { object operand2 = instruction.Operand; TypeReference val2 = (TypeReference)((operand2 is TypeReference) ? operand2 : null); if (val2 != null) { stringBuilder.Append(name + " " + ((MemberReference)val2).FullName); } else { object operand3 = instruction.Operand; FieldReference val3 = (FieldReference)((operand3 is FieldReference) ? operand3 : null); if (val3 != null) { stringBuilder.Append(name + " " + ((MemberReference)((MemberReference)val3).DeclaringType).Name + "." + ((MemberReference)val3).Name); } else { object operand4 = instruction.Operand; VariableDefinition val4 = (VariableDefinition)((operand4 is VariableDefinition) ? operand4 : null); if (val4 != null) { string name2 = ((MemberReference)((VariableReference)val4).VariableType).Name; stringBuilder.Append($"{name} V_{((VariableReference)val4).Index} /* {name2} */"); } else { object operand5 = instruction.Operand; ParameterDefinition val5 = (ParameterDefinition)((operand5 is ParameterDefinition) ? operand5 : null); if (val5 != null) { string name3 = ((MemberReference)((ParameterReference)val5).ParameterType).Name; stringBuilder.Append(name + " " + ((ParameterReference)val5).Name + " /* " + name3 + " */"); } else if (instruction.Operand is string text) { string text2 = text; if (text2.Length > 50) { text2 = text2.Substring(0, 47) + "..."; } stringBuilder.Append(name + " \"" + EscapeStringForCode(text2) + "\""); } else { object operand6 = instruction.Operand; Instruction val6 = (Instruction)((operand6 is Instruction) ? operand6 : null); if (val6 != null) { stringBuilder.Append($"{name} IL_{val6.Offset:X4}"); } else { string text3 = instruction.Operand?.ToString() ?? string.Empty; stringBuilder.Append(name + " " + text3); } } } } } } return stringBuilder.ToString(); } private bool IsSuspiciousMethodCall(MethodReference methodRef) { string fullName = ((MemberReference)((MemberReference)methodRef).DeclaringType).FullName; string name = ((MemberReference)methodRef).Name; if ((!fullName.Contains("Process") || !name.Contains("Start")) && (!fullName.Contains("Assembly") || (!name.Contains("Load") && !name.Contains("LoadFrom") && !name.Contains("LoadFile"))) && (!fullName.Contains("Convert") || !name.Contains("FromBase64")) && ((!fullName.Contains("Registry") && !name.Contains("Reg")) || (!name.Contains("CreateKey") && !name.Contains("OpenKey") && !name.Contains("SetValue") && !name.Contains("GetValue"))) && !fullName.Contains("Shell32") && !name.Contains("ShellExecute")) { if (fullName.Contains("Socket") || fullName.Contains("Http") || fullName.Contains("Tcp") || fullName.Contains("Web") || fullName.Contains("Net")) { if (!name.Contains("Connect") && !name.Contains("Send") && !name.Contains("Download")) { return name.Contains("Upload"); } return true; } return false; } return true; } private string GetSuspiciousMethodDescription(MethodReference methodRef) { string fullName = ((MemberReference)((MemberReference)methodRef).DeclaringType).FullName; string name = ((MemberReference)methodRef).Name; if (fullName.Contains("Process") && name.Contains("Start")) { return "Executes external programs"; } if (fullName.Contains("Assembly") && (name.Contains("Load") || name.Contains("LoadFrom") || name.Contains("LoadFile"))) { return "Dynamically loads code which could be malicious"; } if (fullName.Contains("Convert") && name.Contains("FromBase64")) { return "Decodes potentially obfuscated data"; } if ((fullName.Contains("Registry") || name.Contains("Reg")) && (name.Contains("CreateKey") || name.Contains("OpenKey") || name.Contains("SetValue") || name.Contains("GetValue"))) { return "Manipulates system registry which can persist malware"; } if (fullName.Contains("Shell32") || name.Contains("ShellExecute")) { return "Executes system commands"; } if ((fullName.Contains("Socket") || fullName.Contains("Http") || fullName.Contains("Tcp") || fullName.Contains("Web") || fullName.Contains("Net")) && (name.Contains("Connect") || name.Contains("Send") || name.Contains("Download") || name.Contains("Upload"))) { return "Performs network operations that could exfiltrate data or download malware"; } return "Potentially suspicious behavior"; } private List<string> FindSuspiciousApiCalls(MethodDefinition method) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); Enumerator<Instruction> enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; OpCode opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "call")) { opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "callvirt")) { opCode = current.OpCode; if (!(((OpCode)(ref opCode)).Name == "newobj")) { continue; } } } object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null && IsSuspiciousMethodCall(val)) { list.Add(((MemberReference)((MemberReference)val).DeclaringType).FullName + "." + ((MemberReference)val).Name + "() - " + GetSuspiciousMethodDescription(val)); } } } finally { ((IDisposable)enumerator).Dispose(); } return list.Distinct().ToList(); } private string GetFieldVisibility(FieldDefinition field) { if (field.IsPublic) { return "public"; } if (field.IsPrivate) { return "private"; } if (field.IsFamily) { return "protected"; } if (field.IsFamilyOrAssembly) { return "protected internal"; } if (field.IsAssembly) { return "internal"; } return "private"; } private string GetPropertyVisibility(PropertyDefinition prop) { MethodDefinition getMethod = prop.GetMethod; MethodDefinition setMethod = prop.SetMethod; if ((getMethod != null && getMethod.IsPublic) || (setMethod != null && setMethod.IsPublic)) { return "public"; } if ((getMethod != null && getMethod.IsFamilyOrAssembly) || (setMethod != null && setMethod.IsFamilyOrAssembly)) { return "protected internal"; } if ((getMethod != null && getMethod.IsFamily) || (setMethod != null && setMethod.IsFamily)) { return "protected"; } if ((getMethod != null && getMethod.IsAssembly) || (setMethod != null && setMethod.IsAssembly)) { return "internal"; } if ((getMethod != null && getMethod.IsPrivate) || (setMethod != null && setMethod.IsPrivate)) { return "private"; } if (getMethod == null) { return "public"; } return "public"; } private string GenerateClassStructure(TypeDefinition typeDef, string highlightMethodName = null) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (typeDef == null) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("// Class Outline: " + ((MemberReference)typeDef).FullName); if (typeDef.BaseType != null && ((MemberReference)typeDef.BaseType).FullName != "System.Object") { stringBuilder.AppendLine("// Inherits from: " + ((MemberReference)typeDef.BaseType).FullName); } if (typeDef.HasInterfaces) { Enumerator<InterfaceImplementation> enumerator = typeDef.Interfaces.GetEnumerator(); try { while (enumerator.MoveNext()) { InterfaceImplementation current = enumerator.Current; stringBuilder.AppendLine("// Implements: " + ((MemberReference)current.InterfaceType).FullName); } } finally { ((IDisposable)enumerator).Dispose(); } } string text = "class"; if (typeDef.IsInterface) { text = "interface"; } else if (typeDef.IsEnum) { text = "enum"; } else if (((TypeReference)typeDef).IsValueType) { text = "struct"; } stringBuilder.AppendLine("public " + text + " " + ((MemberReference)typeDef).Name + " // Simplified declaration"); stringBuilder.AppendLine("{"); List<FieldDefinition> list = ((IEnumerable<FieldDefinition>)typeDef.Fields).ToList(); if (list.Any()) { stringBuilder.AppendLine(" // Fields"); foreach (FieldDefinition item in list.Take(10)) { stringBuilder.AppendLine(" " + GetFieldVisibility(item) + " " + (item.IsStatic ? "static " : "") + ((MemberReference)((FieldReference)item).FieldType).Name + " " + ((MemberReference)item).Name + ";"); } if (list.Count > 10) { stringBuilder.AppendLine($" // ... and {list.Count - 10} more fields"); } stringBuilder.AppendLine(); } List<PropertyDefinition> list2 = ((IEnumerable<PropertyDefinition>)typeDef.Properties).ToList(); if (list2.Any()) { stringBuilder.AppendLine(" // Properties"); foreach (PropertyDefinition item2 in list2.Take(10)) { string text2 = "{ "; if (item2.GetMethod != null) { text2 += "get; "; } if (item2.SetMethod != null) { text2 += "set; "; } text2 += "}"; MethodDefinition getMethod = item2.GetMethod; int num; if (getMethod == null || !getMethod.IsStatic) { MethodDefinition setMethod = item2.SetMethod; num = ((setMethod != null && setMethod.IsStatic) ? 1 : 0); } else { num = 1; } bool flag = (byte)num != 0; stringBuilder.AppendLine(" " + GetPropertyVisibility(item2) + " " + (flag ? "static " : "") + ((MemberReference)((PropertyReference)item2).PropertyType).Name + " " + ((MemberReference)item2).Name + " " + text2); } if (list2.Count > 10) { stringBuilder.AppendLine($" // ... and {list2.Count - 10} more properties"); } stringBuilder.AppendLine(); } List<MethodDefinition> list3 = ((IEnumerable<MethodDefinition>)typeDef.Methods).Where((MethodDefinition m) => !m.IsConstructor && !m.IsSpecialName).ToList(); if (list3.Any()) { stringBuilder.AppendLine(" // Methods (signatures only, excluding constructors/property accessors)"); foreach (MethodDefinition item3 in list3.Take(15)) { string text3 = ((highlightMethodName != null && ((MemberReference)item3).Name == highlightMethodName) ? " // <<< Method with finding" : ""); stringBuilder.AppendLine(" " + GetMethodVisibility(item3) + " " + (item3.IsStatic ? "static " : "") + ((MemberReference)((MethodReference)item3).ReturnType).Name + " " + ((MemberReference)item3).Name + "(" + GetMethodParameters(item3) + ");" + text3); } if (list3.Count > 15) { stringBuilder.AppendLine($" // ... and {list3.Count - 15} more methods"); } } stringBuilder.AppendLine("}"); return stringBuilder.ToString(); } private string GetMethodVisibility(MethodDefinition method) { if (method.IsPublic) { return "public"; } if (method.IsPrivate) { return "private"; } if (method.IsFamily) { return "protected"; } if (method.IsFamilyOrAssembly) { return "protected internal"; } return "internal"; } private string GetMethodParameters(MethodDefinition method) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); Enumerator<ParameterDefinition> enumerator = ((MethodReference)method).Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; list.Add(((MemberReference)((ParameterReference)current).ParameterType).Name + " " + ((ParameterReference)current).Name); } } finally { ((IDisposable)enumerator).Dispose(); } return string.Join(", ", list); } private string FormatInstruction(Instruction instruction) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instruction.OpCode; string name = ((OpCode)(ref opCode)).Name; if (name == "nop") { return string.Empty; } string text = FormatOperand(instruction.Operand); return (name + " " + text).Trim(); } private string FormatOperand(object operand) { if (operand == null) { return string.Empty; } MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { return ((MemberReference)((MemberReference)val).DeclaringType).Name + "." + ((MemberReference)val).Name + "()"; } TypeReference val2 = (TypeReference)((operand is TypeReference) ? operand : null); if (val2 != null) { return ((MemberReference)val2).FullName; } FieldReference val3 = (FieldReference)((operand is FieldReference) ? operand : null); if (val3 != null) { return ((MemberReference)((MemberReference)val3).DeclaringType).Name + "." + ((MemberReference)val3).Name; } if (operand is string text) { return "\"" + text + "\""; } return operand.ToString(); } private string ExtractAssemblyMetadata(string modPath) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown try { if (!File.Exists(modPath)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); ReaderParameters val = new ReaderParameters { ReadWrite = false, InMemory = true, ReadSymbols = false }; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(modPath, val); try { stringBuilder.AppendLine("- **Assembly Name**: " + ((AssemblyNameReference)val2.Name).Name); stringBuilder.AppendLine($"- **Version**: {((AssemblyNameReference)val2.Name).Version}"); if (!string.IsNullOrEmpty(((AssemblyNameReference)val2.Name).Culture)) { stringBuilder.AppendLine("- **Culture**: " + ((AssemblyNameReference)val2.Name).Culture); } stringBuilder.AppendLine("- **Referenced Assemblies**:"); foreach (AssemblyNameReference item in ((IEnumerable<AssemblyNameReference>)val2.MainModule.AssemblyReferences).Take(10)) { stringBuilder.AppendLine($" - {item.Name} (v{item.Version})"); } if (val2.MainModule.AssemblyReferences.Count > 10) { stringBuilder.AppendLine($" - *and {val2.MainModule.AssemblyReferences.Count - 10} more...*"); } List<CustomAttribute> list = ((IEnumerable<CustomAttribute>)val2.CustomAttributes).Where((CustomAttribute attr) => ((MemberReference)attr.AttributeType).Name.Contains("Security") || ((MemberReference)attr.AttributeType).Name.Contains("Permission") || ((MemberReference)attr.AttributeType).Name.Contains("Unsafe")).ToList(); if (list.Any()) { stringBuilder.AppendLine("- **Security-Related Attributes**:"); foreach (CustomAttribute item2 in list) { stringBuilder.AppendLine(" - " + ((MemberReference)item2.AttributeType).Name); } } return stringBuilder.ToString(); } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception ex) { _logger.Error("Failed to extract assembly metadata from " + modPath + ": " + ex.Message); return string.Empty; } } public bool SavePromptToFile(string modPath, List<ScanFinding> findings, string outputDirectory) { try { string contents = GeneratePrompt(modPath, findings); string fileName = Path.GetFileName(modPath); Directory.CreateDirectory(outputDirectory); File.WriteAllText(Path.Combine(outputDirectory, fileName + ".prompt.md"), contents); return true; } catch (Exception ex) { _logger.Error("Failed to save prompt to file: " + ex.Message); return false; } } private static string FormatSeverityLabel(Severity severity) { return severity switch { Severity.Critical => "CRITICAL", Severity.High => "HIGH", Severity.Medium => "MEDIUM", Severity.Low => "LOW", _ => severity.ToString().ToUpper(), }; } } } namespace MLVScan.Adapters { public class MelonScanLogger : IScanLogger { private readonly Instance _logger; public MelonScanLogger(Instance logger) { _logger = logger ?? throw new ArgumentNullException("logger"); } public void Debug(string message) { _logger.Msg("[DEBUG] " + message); } public void Info(string message) { _logger.Msg(message); } public void Warning(string message) { _logger.Warning(message); } public void Error(string message) { _logger.Error(message); } public void Error(string message, Exception exception) { _logger.Error($"{message}: {exception}"); } } public class GameAssemblyResolverProvider : IAssemblyResolverProvider { public IAssemblyResolver CreateResolver() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown DefaultAssemblyResolver val = new DefaultAssemblyResolver(); try { string text = Path.Combine(MelonEnvironment.GameRootDirectory, Path.GetFileNameWithoutExtension(MelonEnvironment.GameExecutablePath) + "_Data", "Managed"); if (Directory.Exists(text)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text); } string text2 = Path.Combine(MelonEnvironment.GameRootDirectory, "MelonLoader", "net35"); if (Directory.Exists(text2)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text2); } string text3 = Path.Combine(MelonEnvironment.GameRootDirectory, "MelonLoader", "net6"); if (Directory.Exists(text3)) { ((BaseAssemblyResolver)val).AddSearchDirectory(text3); } string modsDirectory = MelonEnvironment.ModsDirectory; if (Directory.Exists(modsDirectory)) { ((BaseAssemblyResolver)val).AddSearchDirectory(modsDirectory); } string pluginsDirectory = MelonEnvironment.PluginsDirectory; if (Directory.Exists(pluginsDirectory)) { ((BaseAssemblyResolver)val).AddSearchDirectory(pluginsDirectory); } } catch (Exception) { } return (IAssemblyResolver)(object)val; } } } namespace MLVScan.Models { public class DisabledModInfo { public string OriginalPath { get; } public string DisabledPath { get; } public string FileHash { get; } public DisabledModInfo(string originalPath, string disabledPath, string fileHash) { OriginalPath = originalPath; DisabledPath = disabledPath