using System.Text; using System.Text.Json; using System.Text.RegularExpressions; namespace ShrinkNetwork.ServerHost.Framework; public sealed class UnityNetworkScanManifest { public string AssetsPath { get; set; } = string.Empty; public DateTimeOffset ScannedAtUtc { get; set; } public List Messages { get; set; } = new(); public List Subscribers { get; set; } = new(); } public sealed class UnityNetworkMessageEntry { public string FilePath { get; set; } = string.Empty; public string TypeName { get; set; } = string.Empty; public int Opcode { get; set; } public string Route { get; set; } = string.Empty; } public sealed class UnityNetworkSubscriberEntry { public string FilePath { get; set; } = string.Empty; public string MemberName { get; set; } = string.Empty; public string Authority { get; set; } = string.Empty; public string Permission { get; set; } = string.Empty; } public static class UnityNetworkCodeScanner { private static readonly Regex MessageRegex = new( @"\[ShrinkNetworkMessage\((?-?\d+)\s*,\s*""(?[^""]+)""\)\][\s\S]*?(?:class|struct)\s+(?\w+)", RegexOptions.Compiled); private static readonly Regex SubscriberRegex = new( @"\[ShrinkNetworkSubscribe\((?.*?)\)\][\s\S]*?(?:UniTask<.*?>|UniTask|void|Task<.*?>|Task)\s+(?\w+)\s*\(", RegexOptions.Compiled); private static readonly Regex AuthorityRegex = new( @"Authority\s*=\s*ShrinkNetworkAuthority\.(?\w+)", RegexOptions.Compiled); private static readonly Regex PermissionRegex = new( @"Permission\s*=\s*""(?[^""]+)""", RegexOptions.Compiled); public static UnityNetworkScanManifest Scan(string assetsPath) { var manifest = new UnityNetworkScanManifest { AssetsPath = assetsPath, ScannedAtUtc = DateTimeOffset.UtcNow }; if (!Directory.Exists(assetsPath)) return manifest; foreach (var file in Directory.EnumerateFiles(assetsPath, "*.cs", SearchOption.AllDirectories)) { if (file.IndexOf($"{Path.DirectorySeparatorChar}Editor{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) >= 0) continue; var content = File.ReadAllText(file); var relativePath = Path.GetRelativePath(assetsPath, file).Replace('\\', '/'); foreach (Match match in MessageRegex.Matches(content)) { manifest.Messages.Add(new UnityNetworkMessageEntry { FilePath = relativePath, TypeName = match.Groups["name"].Value, Opcode = int.Parse(match.Groups["opcode"].Value), Route = match.Groups["route"].Value }); } foreach (Match match in SubscriberRegex.Matches(content)) { var args = match.Groups["args"].Value; manifest.Subscribers.Add(new UnityNetworkSubscriberEntry { FilePath = relativePath, MemberName = match.Groups["name"].Value, Authority = AuthorityRegex.Match(args).Groups["value"].Value, Permission = PermissionRegex.Match(args).Groups["value"].Value }); } } manifest.Messages = manifest.Messages .OrderBy(item => item.Opcode) .ThenBy(item => item.TypeName, StringComparer.Ordinal) .ToList(); manifest.Subscribers = manifest.Subscribers .OrderBy(item => item.Permission, StringComparer.Ordinal) .ThenBy(item => item.MemberName, StringComparer.Ordinal) .ToList(); return manifest; } public static void WriteOutputs(UnityNetworkScanManifest manifest, string outputDirectory) { Directory.CreateDirectory(outputDirectory); var jsonPath = Path.Combine(outputDirectory, "unity-network-scan.json"); var markdownPath = Path.Combine(outputDirectory, "UNITY_NETWORK_SCAN.md"); File.WriteAllText(jsonPath, JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true })); var markdown = new StringBuilder(); markdown.AppendLine("# Unity 网络代码扫描清单"); markdown.AppendLine(); markdown.AppendLine($"扫描时间:{manifest.ScannedAtUtc:yyyy-MM-dd HH:mm:ss} UTC"); markdown.AppendLine($"Assets 路径:`{manifest.AssetsPath}`"); markdown.AppendLine(); markdown.AppendLine("## 网络消息"); markdown.AppendLine(); foreach (var message in manifest.Messages) markdown.AppendLine($"- `{message.Opcode}` `{message.Route}` `{message.TypeName}` [{message.FilePath}]"); markdown.AppendLine(); markdown.AppendLine("## 订阅与权限"); markdown.AppendLine(); if (manifest.Subscribers.Count == 0) { markdown.AppendLine("- 本次扫描未发现 `[ShrinkNetworkSubscribe]`。"); } else { foreach (var subscriber in manifest.Subscribers) markdown.AppendLine($"- `{subscriber.MemberName}` authority=`{subscriber.Authority}` permission=`{subscriber.Permission}` [{subscriber.FilePath}]"); } File.WriteAllText(markdownPath, markdown.ToString()); } }