using Mono.Cecil; using System.Text; using System.Text.RegularExpressions; using System.Diagnostics; class Program { // Global sets static HashSet _mergedAssemblies = new HashSet(); static HashSet _mergedSystemTypes = new HashSet(); static string _currentScanDirectory = ""; static void Main(string[] args) { // ========================================================= // 🚀 MODE 1: CLI Automation Mode (被 VS 编译调用时) // ========================================================= if (args.Length > 0) { try { string scanPath = args[0].Replace("\"", "").Trim(); string targetNs = args.Length > 1 ? args[1] : "BetterLyrics.WinUI3"; string prefix = args.Length > 2 ? args[2] : "Plugin"; // 读取第4个参数作为输出目录,如果没传,就默认用扫描目录 string outputDir = args.Length > 3 ? args[3].Replace("\"", "").Trim() : scanPath; Console.WriteLine($"[Analyzer] Scanning: {scanPath}"); Console.WriteLine($"[Analyzer] Output to: {outputDir}"); RunBatch(scanPath, targetNs, prefix, outputDir, silent: true); } catch (Exception ex) { Console.WriteLine($"[Analyzer] Error: {ex.Message}"); Environment.Exit(1); } return; } // ========================================================= // 🚀 MODE 2: Interactive Mode (手动双击运行) // ========================================================= Console.WriteLine("=================================================="); Console.WriteLine(" Plugin Dependency Analyzer (Auto-CLI Ready) "); Console.WriteLine("=================================================="); while (true) { _mergedAssemblies.Clear(); _mergedSystemTypes.Clear(); Console.WriteLine("\n1. Enter Plugin FOLDER path (or 'exit'):"); Console.Write("> "); string input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; if (input.Trim().ToLower() == "exit") break; string path = input.Replace("\"", "").Trim(); if (!Directory.Exists(path)) { Console.WriteLine("[ERROR] Folder not found."); continue; } Console.WriteLine("2. Enter Namespace (Default: BetterLyrics.WinUI3):"); Console.Write("> "); string ns = Console.ReadLine().Trim(); if (string.IsNullOrWhiteSpace(ns)) ns = "BetterLyrics.WinUI3"; string prefix = new DirectoryInfo(path).Name; Console.WriteLine($"3. Enter prefix (Default: {prefix}):"); Console.Write("> "); string p = Console.ReadLine().Trim(); if (!string.IsNullOrWhiteSpace(p)) prefix = p; // 👇👇👇【修复点在这里】👇👇👇 // 交互模式下,我们默认把文件生成在 exe 旁边的 AnalyzerOutput 文件夹里 string interactiveOutputDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AnalyzerOutput"); // 传入 interactiveOutputDir 以匹配新的方法签名 RunBatch(path, ns, prefix, interactiveOutputDir, silent: false); } } // --------------------------------------------------------- // 下面的 RunBatch 和 GenerateFiles 签名已更新支持 outputDir // --------------------------------------------------------- static void RunBatch(string scanPath, string ns, string prefix, string outputDir, bool silent) { _currentScanDirectory = scanPath; _mergedAssemblies.Clear(); _mergedSystemTypes.Clear(); ProcessDirectory(scanPath); // 传递 outputDir GenerateFiles(prefix, ns, outputDir, silent); } static void GenerateFiles(string prefix, string rootNamespace, string outputDir, bool silent) { Directory.CreateDirectory(outputDir); string xmlFileName = $"{prefix}_TrimmerRoots.xml"; string csFileName = $"{prefix}_TrimmingConfig.cs"; // 👇 关键修改:区分 Assembly 名和 Namespace 名 string assemblyName = rootNamespace; // DLL 名字 (例如 BetterLyrics.WinUI3) string codeNamespace = $"{rootNamespace}.PluginConfigs"; // C# 命名空间 (例如 BetterLyrics.WinUI3.PluginConfigs) // 类名和全名 string configClassName = $"{prefix.Replace(".", "_").Replace(" ", "_")}_Config"; string fullConfigClassName = $"{codeNamespace}.{configClassName}"; // 路径变量 string xmlPath = Path.Combine(outputDir, xmlFileName); string csPath = Path.Combine(outputDir, csFileName); // ========================================== // 1. Generate XML // ========================================== var xmlBuilder = new StringBuilder(); xmlBuilder.AppendLine(""); xmlBuilder.AppendLine(""); // A. 保护插件依赖 if (_mergedAssemblies.Count > 0) { foreach (var asm in _mergedAssemblies.OrderBy(n => n)) xmlBuilder.AppendLine($" "); } // B. 保护生成的 Config 类 // ⚠️ 注意:assembly fullname 必须是 rootNamespace (DLL名),而不是 codeNamespace xmlBuilder.AppendLine($" "); xmlBuilder.AppendLine($" "); xmlBuilder.AppendLine($" "); xmlBuilder.AppendLine(""); // ========================================== // 2. Generate C# // ========================================== var csBuilder = new StringBuilder(); csBuilder.AppendLine("// Auto-Generated by PluginAnalyzer"); csBuilder.AppendLine("using System.Diagnostics.CodeAnalysis;"); csBuilder.AppendLine("using System.Runtime.CompilerServices;"); csBuilder.AppendLine(""); // 👇 这里使用带后缀的命名空间 csBuilder.AppendLine($"namespace {codeNamespace};"); csBuilder.AppendLine($"internal static class {configClassName}"); csBuilder.AppendLine("{"); foreach (var type in _mergedSystemTypes.OrderBy(n => n)) { csBuilder.AppendLine($" [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof({type}))]"); } csBuilder.AppendLine(" [ModuleInitializer]"); csBuilder.AppendLine(" internal static void Initialize()"); csBuilder.AppendLine(" {"); csBuilder.AppendLine(" // This method runs automatically on startup."); csBuilder.AppendLine(" }"); csBuilder.AppendLine("}"); // ========================================== // 3. Write & Notify // ========================================== File.WriteAllText(xmlPath, xmlBuilder.ToString()); File.WriteAllText(csPath, csBuilder.ToString()); if (!silent) { Console.WriteLine($"[Analyzer] Generated: {xmlFileName}"); Process.Start("explorer.exe", outputDir); } else { Console.WriteLine($"[Analyzer] Created: {xmlPath}"); Console.WriteLine($"[Analyzer] Created: {csPath}"); } } // --------------------------------------------------------- // Helpers (逻辑保持不变) // --------------------------------------------------------- static void ProcessDirectory(string folderPath) { var files = Directory.GetFiles(folderPath, "*.dll"); foreach (var file in files) { if (!IsHostOrSystemBinary(Path.GetFileName(file))) AnalyzeDll(file); } } static void AnalyzeDll(string path) { try { var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(Path.GetDirectoryName(path)); using var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { AssemblyResolver = resolver }); foreach (var typeRef in assembly.MainModule.GetTypeReferences()) { if (typeRef.Scope is AssemblyNameReference asmRef && !IsIgnoredDependency(asmRef.Name) && !IsLocalDll(asmRef.Name)) _mergedAssemblies.Add(asmRef.Name); if (typeRef.FullName.StartsWith("System.") || typeRef.FullName.StartsWith("Microsoft.Win32")) _mergedSystemTypes.Add(FormatTypeName(typeRef)); } } catch { } } static bool IsLocalDll(string asmName) => File.Exists(Path.Combine(_currentScanDirectory, asmName + ".dll")); static bool IsHostOrSystemBinary(string fileName) { fileName = fileName.ToLower(); return fileName.StartsWith("betterlyrics.") && !fileName.Contains("plugin") || fileName.StartsWith("microsoft.windows") || fileName.StartsWith("winrt.") || fileName.StartsWith("system."); } static bool IsIgnoredDependency(string asmName) => asmName.Contains("BetterLyrics") || asmName.Contains("WindowsAppSDK") || asmName.Contains("Microsoft.Windows") || asmName == "netstandard" || asmName == "mscorlib"; static string FormatTypeName(TypeReference type) { string name = type.FullName; // 1. 处理嵌套类型 (Cecil 用 '/',C# 用 '.') name = name.Replace("/", "."); // 2. 处理泛型 (核心修复:根据参数数量动态生成逗号) // List`1 -> List<> // Dictionary`2 -> Dictionary<,> // Tuple`3 -> Tuple<,,> if (name.Contains("`")) { name = Regex.Replace(name, @"`(\d+)", match => { int argsCount = int.Parse(match.Groups[1].Value); // 逗号数量 = 参数数量 - 1 // 比如 1个参数=0个逗号 (<>) // 比如 2个参数=1个逗号 (<,>) return "<" + new string(',', Math.Max(0, argsCount - 1)) + ">"; }); } return name; } }