| using System;  | 
| using System.Collections.Generic;  | 
| using System.IO;  | 
| using System.Linq;  | 
| using System.Security.Cryptography;  | 
| using System.Threading.Tasks;  | 
| using UnityEditor;  | 
| using UnityEngine;  | 
|   | 
| namespace ProjSG.Editor.UI  | 
| {  | 
|     public class AssetDuplicateCheck : EditorWindow  | 
|     {  | 
|         private Vector2 scrollPosition;  | 
|         private bool showSettings = true;  | 
|         private bool showResults = false;  | 
|         private bool showFullPath = false;  | 
|           | 
|         private string[] searchFolders = new string[] { "Assets" };  | 
|         private string[] searchExtensions = new string[] { ".png", ".jpg", ".jpeg", ".tga", ".psd", ".tif", ".tiff" };  | 
|         private bool includeSubfolders = true;  | 
|         private bool compareByContent = true;  | 
|         private bool compareByName = false;  | 
|         private int minFileSize = 10;  | 
|           | 
|         private Dictionary<string, List<string>> duplicateGroups = new Dictionary<string, List<string>>();  | 
|         private int totalFilesScanned = 0;  | 
|         private int totalDuplicatesFound = 0;  | 
|         private long totalSpaceSaved = 0;  | 
|           | 
|         private bool keepNewestFiles = true;  | 
|         private bool createBackup = true;  | 
|         private string backupFolder = "";  | 
|         private Dictionary<string, bool> selectedForRemoval = new Dictionary<string, bool>();  | 
|           | 
|         private bool isProcessing = false;  | 
|         private float progress = 0f;  | 
|         private string progressInfo = "";  | 
|   | 
|         [MenuItem("工具/资源管理/资源查重工具")]  | 
|         public static void ShowWindow()  | 
|         {  | 
|             AssetDuplicateCheck window = GetWindow<AssetDuplicateCheck>("资源查重工具");  | 
|             window.minSize = new Vector2(600, 400);  | 
|             window.Show();  | 
|         }  | 
|   | 
|         private string GetAssetPath(string filePath)  | 
|         {  | 
|             string fullPath = Path.GetFullPath(filePath);  | 
|             string projectDataPath = Path.GetFullPath(Application.dataPath);  | 
|   | 
|             if (fullPath.StartsWith(projectDataPath, StringComparison.OrdinalIgnoreCase))  | 
|             {  | 
|                 return "Assets" + fullPath.Substring(projectDataPath.Length).Replace('\\', '/');  | 
|             }  | 
|               | 
|             string projectRootPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));  | 
|             if (fullPath.StartsWith(projectRootPath, StringComparison.OrdinalIgnoreCase))  | 
|             {  | 
|                 string relativeToProjectRoot = fullPath.Substring(projectRootPath.Length);  | 
|                 if (relativeToProjectRoot.StartsWith("/") || relativeToProjectRoot.StartsWith("\\"))  | 
|                 {  | 
|                     relativeToProjectRoot = relativeToProjectRoot.Substring(1);  | 
|                 }  | 
|                 return relativeToProjectRoot.Replace('\\', '/');  | 
|             }  | 
|   | 
|             Debug.LogWarning($"路径 '{filePath}' 无法可靠地转换为项目相对的资产路径。将返回原始路径(已规范化斜杠)。");  | 
|             return filePath.Replace('\\', '/');  | 
|         }  | 
|   | 
|         private void OnGUI()  | 
|         {  | 
|             GUILayout.Label("资源查重工具", EditorStyles.boldLabel);  | 
|             EditorGUILayout.Space();  | 
|   | 
|             scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);  | 
|   | 
|             showSettings = EditorGUILayout.Foldout(showSettings, "搜索设置", true);  | 
|             if (showSettings)  | 
|             {  | 
|                 DrawSettings();  | 
|             }  | 
|   | 
|             EditorGUILayout.Space();  | 
|   | 
|             if (duplicateGroups.Count > 0)  | 
|             {  | 
|                 showResults = EditorGUILayout.Foldout(showResults, $"搜索结果 (找到 {totalDuplicatesFound} 个重复资源,可节省 {totalSpaceSaved / 1024f:F2} MB)", true);  | 
|                 if (showResults)  | 
|                 {  | 
|                     DrawResults();  | 
|                 }  | 
|             }  | 
|   | 
|             EditorGUILayout.EndScrollView();  | 
|   | 
|             EditorGUILayout.Space();  | 
|   | 
|             if (isProcessing)  | 
|             {  | 
|                 EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(false, 20f), progress, progressInfo);  | 
|                 if (GUILayout.Button("取消"))  | 
|                 {  | 
|                     isProcessing = false;  | 
|                 }  | 
|             }  | 
|             else  | 
|             {  | 
|                 EditorGUILayout.BeginHorizontal();  | 
|                 if (GUILayout.Button("扫描重复资源", GUILayout.Height(30)))  | 
|                 {  | 
|                     FindDuplicateAssets();  | 
|                 }  | 
|   | 
|                 GUI.enabled = duplicateGroups.Count > 0;  | 
|                 if (GUILayout.Button("处理选中的重复资源", GUILayout.Height(30)))  | 
|                 {  | 
|                     ProcessDuplicateAssets();  | 
|                 }  | 
|                 GUI.enabled = true;  | 
|                 EditorGUILayout.EndHorizontal();  | 
|             }  | 
|         }  | 
|   | 
|         private void DrawSettings()  | 
|         {  | 
|             EditorGUI.indentLevel++;  | 
|   | 
|             EditorGUILayout.LabelField("搜索文件夹:");  | 
|             EditorGUILayout.BeginHorizontal();  | 
|             string displaySearchFolder = searchFolders.Length > 0 ? searchFolders[0] : "Assets";  | 
|             EditorGUILayout.SelectableLabel(displaySearchFolder, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));  | 
|               | 
|             if (GUILayout.Button("选择文件夹", GUILayout.Width(100)))  | 
|             {  | 
|                 string folderPath = EditorUtility.OpenFolderPanel("选择要搜索的文件夹", Application.dataPath, "");  | 
|                 if (!string.IsNullOrEmpty(folderPath))  | 
|                 {  | 
|                     string relativePath = GetAssetPath(folderPath);  | 
|                     if (!string.IsNullOrEmpty(relativePath) &&   | 
|                         (relativePath.StartsWith("Assets", StringComparison.OrdinalIgnoreCase) ||   | 
|                          relativePath.StartsWith("Packages", StringComparison.OrdinalIgnoreCase) ||   | 
|                          Directory.Exists(Path.Combine(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), relativePath))))  | 
|                     {  | 
|                         searchFolders = new string[] { relativePath };  | 
|                     }  | 
|                     else  | 
|                     {  | 
|                         Debug.LogWarning($"选择的文件夹 '{folderPath}' 不在项目 Assets 或 Packages 内,或无法转换为有效的项目相对路径。");  | 
|                     }  | 
|                 }  | 
|             }  | 
|             EditorGUILayout.EndHorizontal();  | 
|   | 
|             EditorGUILayout.LabelField("文件类型:");  | 
|             string extensionsString = string.Join(", ", searchExtensions);  | 
|             extensionsString = EditorGUILayout.TextField(extensionsString);  | 
|             searchExtensions = extensionsString.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)  | 
|                 .Select(e => e.Trim().ToLower())  | 
|                 .Select(e => e.StartsWith(".") ? e : "." + e)  | 
|                 .Distinct()  | 
|                 .ToArray();  | 
|   | 
|             EditorGUILayout.Space();  | 
|             EditorGUILayout.LabelField("搜索选项:");  | 
|             includeSubfolders = EditorGUILayout.Toggle("包含子文件夹", includeSubfolders);  | 
|             compareByContent = EditorGUILayout.Toggle("按内容比较", compareByContent);  | 
|             compareByName = EditorGUILayout.Toggle("按名称比较", compareByName);  | 
|             minFileSize = EditorGUILayout.IntField("最小文件大小 (KB)", minFileSize);  | 
|               | 
|             EditorGUILayout.Space();  | 
|             EditorGUILayout.LabelField("处理选项:");  | 
|             keepNewestFiles = EditorGUILayout.Toggle("保留最新文件", keepNewestFiles);  | 
|             createBackup = EditorGUILayout.Toggle("创建备份", createBackup);  | 
|               | 
|             if (createBackup)  | 
|             {  | 
|                 EditorGUILayout.BeginHorizontal();  | 
|                 backupFolder = EditorGUILayout.TextField("备份文件夹:", backupFolder);  | 
|                 if (GUILayout.Button("浏览", GUILayout.Width(60)))  | 
|                 {  | 
|                     string path = EditorUtility.OpenFolderPanel("选择备份文件夹", backupFolder, "");  | 
|                     if (!string.IsNullOrEmpty(path))  | 
|                     {  | 
|                         backupFolder = path;  | 
|                     }  | 
|                 }  | 
|                 EditorGUILayout.EndHorizontal();  | 
|             }  | 
|   | 
|             EditorGUI.indentLevel--;  | 
|         }  | 
|   | 
|         private void DrawResults()  | 
|         {  | 
|             EditorGUI.indentLevel++;  | 
|               | 
|             EditorGUILayout.BeginHorizontal();  | 
|             showFullPath = EditorGUILayout.Toggle("显示完整路径", showFullPath);  | 
|               | 
|             GUILayout.FlexibleSpace();  | 
|               | 
|             if (GUILayout.Button("全选", GUILayout.Width(60)))  | 
|             {  | 
|                 SelectAll(true);  | 
|             }  | 
|               | 
|             if (GUILayout.Button("全不选", GUILayout.Width(60)))  | 
|             {  | 
|                 SelectAll(false);  | 
|             }  | 
|               | 
|             if (GUILayout.Button("智能选择", GUILayout.Width(80)))  | 
|             {  | 
|                 SmartSelect();  | 
|             }  | 
|               | 
|             EditorGUILayout.EndHorizontal();  | 
|               | 
|             EditorGUILayout.Space();  | 
|               | 
|             int groupIndex = 0;  | 
|             foreach (var group in duplicateGroups)  | 
|             {  | 
|                 if (group.Value.Count <= 1) continue;  | 
|                   | 
|                 groupIndex++;  | 
|                 EditorGUILayout.BeginVertical(GUI.skin.box);  | 
|                   | 
|                 string groupTitle = $"组 {groupIndex}: {group.Value.Count} 个文件";  | 
|                 if (group.Value.Count > 0)  | 
|                 {  | 
|                     string fileName = Path.GetFileName(group.Value[0]);  | 
|                     groupTitle += $" - {fileName}";  | 
|                 }  | 
|                   | 
|                 EditorGUILayout.LabelField(groupTitle, EditorStyles.boldLabel);  | 
|                   | 
|                 foreach (string filePath in group.Value)  | 
|                 {  | 
|                     EditorGUILayout.BeginHorizontal();  | 
|                       | 
|                     bool isSelected = false;  | 
|                     if (selectedForRemoval.ContainsKey(filePath))  | 
|                     {  | 
|                         isSelected = selectedForRemoval[filePath];  | 
|                     }  | 
|                       | 
|                     bool newSelected = EditorGUILayout.Toggle(isSelected, GUILayout.Width(20));  | 
|                     if (newSelected != isSelected)  | 
|                     {  | 
|                         selectedForRemoval[filePath] = newSelected;  | 
|                     }  | 
|                       | 
|                     string displayPath = showFullPath ? filePath : Path.GetFileName(filePath);  | 
|                       | 
|                     FileInfo fileInfo = new FileInfo(Path.Combine(Application.dataPath, "..", filePath));  | 
|                     string fileDetails = $"{displayPath} - {fileInfo.Length / 1024} KB";  | 
|                       | 
|                     if (fileInfo.Exists)  | 
|                     {  | 
|                         fileDetails += $" - {fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")}";  | 
|                     }  | 
|                       | 
|                     EditorGUILayout.LabelField(fileDetails);  | 
|                       | 
|                     if (GUILayout.Button("选择", GUILayout.Width(60)))  | 
|                     {  | 
|                         Selection.activeObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(filePath);  | 
|                     }  | 
|                       | 
|                     EditorGUILayout.EndHorizontal();  | 
|                 }  | 
|                   | 
|                 EditorGUILayout.EndVertical();  | 
|                 EditorGUILayout.Space();  | 
|             }  | 
|               | 
|             EditorGUI.indentLevel--;  | 
|         }  | 
|   | 
|         private void SelectAll(bool select)  | 
|         {  | 
|             foreach (var group in duplicateGroups)  | 
|             {  | 
|                 if (group.Value.Count <= 1) continue;  | 
|                   | 
|                 foreach (string filePath in group.Value)  | 
|                 {  | 
|                     selectedForRemoval[filePath] = select;  | 
|                 }  | 
|             }  | 
|         }  | 
|   | 
|         private void SmartSelect()  | 
|         {  | 
|             foreach (var group in duplicateGroups)  | 
|             {  | 
|                 if (group.Value.Count <= 1) continue;  | 
|                   | 
|                 string fileToKeep = null;  | 
|                   | 
|                 if (keepNewestFiles)  | 
|                 {  | 
|                     fileToKeep = group.Value  | 
|                         .OrderByDescending(f => File.GetLastWriteTime(Path.Combine(Application.dataPath, "..", f)))  | 
|                         .FirstOrDefault();  | 
|                 }  | 
|                 else  | 
|                 {  | 
|                     fileToKeep = group.Value.FirstOrDefault();  | 
|                 }  | 
|                   | 
|                 foreach (string filePath in group.Value)  | 
|                 {  | 
|                     selectedForRemoval[filePath] = (filePath != fileToKeep);  | 
|                 }  | 
|             }  | 
|         }  | 
|   | 
|         // 计算文件的MD5哈希值  | 
|         private string GetFileHash(string filePath)  | 
|         {  | 
|             try  | 
|             {  | 
|                 using (var md5 = MD5.Create())  | 
|                 using (var stream = File.OpenRead(filePath))  | 
|                 {  | 
|                     byte[] hash = md5.ComputeHash(stream);  | 
|                     return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();  | 
|                 }  | 
|             }  | 
|             catch (Exception ex)  | 
|             {  | 
|                 Debug.LogError($"计算文件哈希值时出错: {filePath}, {ex.Message}");  | 
|                 return string.Empty;  | 
|             }  | 
|         }  | 
|   | 
|         private async void FindDuplicateAssets()  | 
|         {  | 
|             isProcessing = true;  | 
|             progress = 0f;  | 
|             progressInfo = "正在收集文件...";  | 
|               | 
|             // 清空之前的结果  | 
|             duplicateGroups.Clear();  | 
|             selectedForRemoval.Clear();  | 
|             totalFilesScanned = 0;  | 
|             totalDuplicatesFound = 0;  | 
|             totalSpaceSaved = 0;  | 
|               | 
|             try  | 
|             {  | 
|                 // 收集所有文件  | 
|                 List<string> allFiles = new List<string>();  | 
|                 foreach (string folder in searchFolders)  | 
|                 {  | 
|                     if (string.IsNullOrEmpty(folder)) continue;  | 
|                       | 
|                     string fullPath = Path.Combine(Application.dataPath, "..", folder);  | 
|                     if (!Directory.Exists(fullPath)) continue;  | 
|                       | 
|                     string[] files = Directory.GetFiles(fullPath, "*.*", includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)  | 
|                         .Where(f => searchExtensions.Contains(Path.GetExtension(f).ToLower()))  | 
|                         .Select(f => GetAssetPath(f))  | 
|                         .ToArray();  | 
|                       | 
|                     allFiles.AddRange(files);  | 
|                 }  | 
|                   | 
|                 totalFilesScanned = allFiles.Count;  | 
|                 progressInfo = $"找到 {totalFilesScanned} 个文件,正在分析...";  | 
|                 progress = 0.1f;  | 
|                 Repaint();  | 
|                   | 
|                 // 按内容或名称分组  | 
|                 Dictionary<string, List<string>> groups = new Dictionary<string, List<string>>();  | 
|                   | 
|                 for (int i = 0; i < allFiles.Count; i++)  | 
|                 {  | 
|                     string filePath = allFiles[i];  | 
|                     string fullPath = Path.Combine(Application.dataPath, "..", filePath);  | 
|                       | 
|                     // 检查文件大小  | 
|                     FileInfo fileInfo = new FileInfo(fullPath);  | 
|                     if (!fileInfo.Exists || fileInfo.Length < minFileSize * 1024) continue;  | 
|                       | 
|                     string key = "";  | 
|                       | 
|                     if (compareByContent)  | 
|                     {  | 
|                         // 计算文件哈希值  | 
|                         key = GetFileHash(fullPath);  | 
|                     }  | 
|                     else if (compareByName)  | 
|                     {  | 
|                         // 使用文件名作为键  | 
|                         key = Path.GetFileNameWithoutExtension(filePath);  | 
|                     }  | 
|                       | 
|                     if (string.IsNullOrEmpty(key)) continue;  | 
|                       | 
|                     if (!groups.ContainsKey(key))  | 
|                     {  | 
|                         groups[key] = new List<string>();  | 
|                     }  | 
|                       | 
|                     groups[key].Add(filePath);  | 
|                       | 
|                     // 更新进度  | 
|                     if (i % 10 == 0)  | 
|                     {  | 
|                         progress = 0.1f + 0.8f * ((float)i / allFiles.Count);  | 
|                         progressInfo = $"正在分析文件 {i+1}/{allFiles.Count}...";  | 
|                         Repaint();  | 
|                         await Task.Delay(1);  | 
|                     }  | 
|                 }  | 
|                   | 
|                 // 过滤出重复组  | 
|                 foreach (var group in groups)  | 
|                 {  | 
|                     if (group.Value.Count > 1)  | 
|                     {  | 
|                         duplicateGroups[group.Key] = group.Value;  | 
|                         totalDuplicatesFound += group.Value.Count - 1;  | 
|                           | 
|                         // 计算可节省空间  | 
|                         string firstFile = group.Value[0];  | 
|                         string fullPath = Path.Combine(Application.dataPath, "..", firstFile);  | 
|                         FileInfo fileInfo = new FileInfo(fullPath);  | 
|                         if (fileInfo.Exists)  | 
|                         {  | 
|                             totalSpaceSaved += (group.Value.Count - 1) * fileInfo.Length / 1024;  | 
|                         }  | 
|                     }  | 
|                 }  | 
|                   | 
|                 progressInfo = $"分析完成,找到 {totalDuplicatesFound} 个重复资源";  | 
|                 progress = 1f;  | 
|                 Repaint();  | 
|                   | 
|                 // 自动选择  | 
|                 if (duplicateGroups.Count > 0)  | 
|                 {  | 
|                     SmartSelect();  | 
|                 }  | 
|             }  | 
|             catch (Exception ex)  | 
|             {  | 
|                 Debug.LogError($"查找重复资源时出错: {ex.Message}\n{ex.StackTrace}");  | 
|                 EditorUtility.DisplayDialog("错误", $"查找重复资源时出错: {ex.Message}", "确定");  | 
|             }  | 
|             finally  | 
|             {  | 
|                 isProcessing = false;  | 
|                 Repaint();  | 
|             }  | 
|         }  | 
|   | 
|         private async void ProcessDuplicateAssets()  | 
|         {  | 
|             isProcessing = true;  | 
|             progress = 0f;  | 
|             progressInfo = "正在准备处理...";  | 
|             Repaint();  | 
|   | 
|             // 统计要处理的文件数量  | 
|             int totalToProcess = selectedForRemoval.Count(pair => pair.Value);  | 
|             if (totalToProcess == 0)  | 
|             {  | 
|                 EditorUtility.DisplayDialog("提示", "没有选择任何文件进行处理", "确定");  | 
|                 isProcessing = false;  | 
|                 return;  | 
|             }  | 
|               | 
|             // 确认操作  | 
|             bool confirm = EditorUtility.DisplayDialog("确认操作",   | 
|                 $"将处理 {totalToProcess} 个重复资源。\n" +  | 
|                 (createBackup ? $"备份将保存到: {backupFolder}" : "不创建备份") +  | 
|                 "\n\n此操作不可撤销,是否继续?",   | 
|                 "继续", "取消");  | 
|                   | 
|             if (!confirm)  | 
|             {  | 
|                 isProcessing = false;  | 
|                 return;  | 
|             }  | 
|               | 
|             try  | 
|             {  | 
|                 // 处理每个重复组  | 
|                 foreach (var group in duplicateGroups)  | 
|                 {  | 
|                     if (group.Value.Count <= 1) continue;  | 
|                       | 
|                     // 找出要保留的文件(通常是最新的文件)  | 
|                     string keepFile = null;  | 
|                     if (keepNewestFiles)  | 
|                     {  | 
|                         keepFile = group.Value  | 
|                             .OrderByDescending(f => new FileInfo(Path.Combine(Application.dataPath, "..", f)).LastWriteTime)  | 
|                             .FirstOrDefault();  | 
|                     }  | 
|                     else  | 
|                     {  | 
|                         // 如果不是保留最新的,则保留第一个未被选中删除的文件  | 
|                         keepFile = group.Value.FirstOrDefault(f => !selectedForRemoval.ContainsKey(f) || !selectedForRemoval[f]);  | 
|                     }  | 
|                       | 
|                     // 如果没有找到要保留的文件,跳过这个组  | 
|                     if (string.IsNullOrEmpty(keepFile))   | 
|                     {  | 
|                         throw new Exception("没有找到要保留的文件");  | 
|                         continue;  | 
|                     }  | 
|                     // 处理这个组中的其他文件  | 
|                     foreach (string filePath in group.Value)  | 
|                     {  | 
|                         if (filePath == keepFile) continue;  | 
|                           | 
|                         // 检查是否选中了这个文件进行删除  | 
|                         if (!selectedForRemoval.ContainsKey(filePath) || !selectedForRemoval[filePath]) continue;  | 
|                           | 
|                         // 更新引用关系,将引用从filePath重定向到keepFile  | 
|                         UpdateReferences(filePath, keepFile);  | 
|                           | 
|                         // ... existing code ...  | 
|                           | 
|                         // 创建备份和删除文件的代码  | 
|                     }  | 
|                 }  | 
|   | 
|                 // 刷新资源数据库  | 
|                 AssetDatabase.Refresh();  | 
|   | 
|                 // 创建备份文件夹  | 
|                 if (createBackup && !string.IsNullOrEmpty(backupFolder))  | 
|                 {  | 
|                     Directory.CreateDirectory(backupFolder);  | 
|                 }  | 
|                   | 
|                 // 收集要删除的文件和保留的文件  | 
|                 List<string> filesToRemove = new List<string>();  | 
|                 Dictionary<string, string> fileReplaceMap = new Dictionary<string, string>();  | 
|                   | 
|                 var tempGroups = new Dictionary<string, List<string>>(duplicateGroups);  | 
|                 var tempRemoval = new Dictionary<string, bool>(selectedForRemoval);  | 
|   | 
|                 // 清空结果数据  | 
|                 duplicateGroups.Clear();  | 
|                 selectedForRemoval.Clear();  | 
|                 totalFilesScanned = 0;  | 
|                 totalDuplicatesFound = 0;  | 
|                 totalSpaceSaved = 0;  | 
|                 showResults = false;  | 
|   | 
|                 foreach (var group in tempGroups)  | 
|                 {  | 
|                     if (group.Value.Count <= 1) continue;  | 
|                       | 
|                     // 找出要保留的文件  | 
|                     string fileToKeep = null;  | 
|                       | 
|                     foreach (string filePath in group.Value)  | 
|                     {  | 
|                         if (!tempRemoval.ContainsKey(filePath) || !tempRemoval[filePath])  | 
|                         {  | 
|                             fileToKeep = filePath;  | 
|                             break;  | 
|                         }  | 
|                     }  | 
|                       | 
|                     // 如果所有文件都被标记为删除,保留第一个  | 
|                     if (string.IsNullOrEmpty(fileToKeep) && group.Value.Count > 0)  | 
|                     {  | 
|                         if (keepNewestFiles)  | 
|                         {  | 
|                             // 按最后修改时间排序,保留最新的  | 
|                             fileToKeep = group.Value  | 
|                                 .OrderByDescending(f => File.GetLastWriteTime(Path.Combine(Application.dataPath, "..", f)))  | 
|                                 .FirstOrDefault();  | 
|                         }  | 
|                         else  | 
|                         {  | 
|                             fileToKeep = group.Value[0];  | 
|                         }  | 
|                           | 
|                         if (tempRemoval.ContainsKey(fileToKeep))  | 
|                         {  | 
|                             tempRemoval[fileToKeep] = false;  | 
|                         }  | 
|                     }  | 
|                       | 
|                     // 收集要删除的文件  | 
|                     foreach (string filePath in group.Value)  | 
|                     {  | 
|                         if (filePath != fileToKeep && tempRemoval.ContainsKey(filePath) && tempRemoval[filePath])  | 
|                         {  | 
|                             filesToRemove.Add(filePath);  | 
|                             fileReplaceMap[filePath] = fileToKeep;  | 
|                         }  | 
|                     }  | 
|                 }  | 
|                   | 
|                 // 处理文件  | 
|                 int processed = 0;  | 
|                 foreach (string filePath in filesToRemove)  | 
|                 {  | 
|                     processed++;  | 
|                     progress = (float)processed / totalToProcess;  | 
|                     progressInfo = $"正在处理 {processed}/{totalToProcess}: {Path.GetFileName(filePath)}";  | 
|                     Repaint();  | 
|                       | 
|                     try  | 
|                     {  | 
|                         // 备份文件  | 
|                         if (createBackup && !string.IsNullOrEmpty(backupFolder))  | 
|                         {  | 
|                             string backupPath = Path.Combine(backupFolder, Path.GetFileName(filePath));  | 
|                             File.Copy(Path.Combine(Application.dataPath, "..", filePath), backupPath, true);  | 
|                         }  | 
|                           | 
|                         // 更新引用  | 
|                         if (fileReplaceMap.ContainsKey(filePath))  | 
|                         {  | 
|                             UpdateReferences(filePath, fileReplaceMap[filePath]);  | 
|                         }  | 
|                           | 
|                         // 删除文件  | 
|                         AssetDatabase.DeleteAsset(filePath);  | 
|                           | 
|                         await Task.Delay(1);  | 
|                     }  | 
|                     catch (Exception ex)  | 
|                     {  | 
|                         Debug.LogError($"处理文件时出错: {filePath}, {ex.Message}");  | 
|                     }  | 
|                 }  | 
|                   | 
|                 // 刷新资源数据库  | 
|                 AssetDatabase.Refresh();  | 
|                   | 
|   | 
|                   | 
|                 // 完成  | 
|                 progressInfo = $"处理完成,已处理 {processed} 个文件";  | 
|                 progress = 1f;  | 
|                 EditorUtility.DisplayDialog("完成", $"已处理 {processed} 个重复资源", "确定");  | 
|             }  | 
|             catch (Exception ex)  | 
|             {  | 
|                 Debug.LogError($"处理重复资源时出错: {ex.Message}\n{ex.StackTrace}");  | 
|                 EditorUtility.DisplayDialog("错误", $"处理重复资源时出错: {ex.Message}", "确定");  | 
|             }  | 
|             finally  | 
|             {  | 
|                 isProcessing = false;  | 
|                 Repaint();  | 
|             }  | 
|         }  | 
|   | 
|         // 更新引用关系  | 
|         private void UpdateReferences(string oldPath, string newPath)  | 
|         {  | 
|             string[] guids = AssetDatabase.FindAssets("t:Object");  | 
|               | 
|             foreach (string guid in guids)  | 
|             {  | 
|                 string assetPath = AssetDatabase.GUIDToAssetPath(guid);  | 
|                   | 
|                 if (assetPath == oldPath || assetPath == newPath) continue;  | 
|                   | 
|                 if (!assetPath.EndsWith(".asset") && !assetPath.EndsWith(".prefab") && !assetPath.EndsWith(".unity") && !assetPath.EndsWith(".mat"))  | 
|                     continue;  | 
|                   | 
|                 UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);  | 
|                 if (obj == null) continue;  | 
|                   | 
|                 string[] dependencies = AssetDatabase.GetDependencies(assetPath, false);  | 
|                 if (dependencies.Contains(oldPath))  | 
|                 {  | 
|                     // 获取旧资源和新资源  | 
|                     UnityEngine.Object oldAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(oldPath);  | 
|                     UnityEngine.Object newAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(newPath);  | 
|                       | 
|                     if (oldAsset != null && newAsset != null)  | 
|                     {  | 
|                         // 使用SerializedObject更新引用  | 
|                         SerializedObject serializedObj = new SerializedObject(obj);  | 
|                         bool modified = false;  | 
|                           | 
|                         SerializedProperty iterator = serializedObj.GetIterator();  | 
|                         while (iterator.NextVisible(true))  | 
|                         {  | 
|                             if (iterator.propertyType == SerializedPropertyType.ObjectReference &&   | 
|                                 iterator.objectReferenceValue == oldAsset)  | 
|                             {  | 
|                                 iterator.objectReferenceValue = newAsset;  | 
|                                 modified = true;  | 
|                             }  | 
|                         }  | 
|                           | 
|                         if (modified)  | 
|                         {  | 
|                             serializedObj.ApplyModifiedProperties();  | 
|                             Debug.Log($"已更新资源 {assetPath} 中的引用: {oldPath} -> {newPath}");  | 
|                         }  | 
|                     }  | 
|                       | 
|                     EditorUtility.SetDirty(obj);  | 
|                 }  | 
|             }  | 
|               | 
|             AssetDatabase.SaveAssets();  | 
|         }  | 
|     }  | 
| }  | 
|   |