using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace TcgEngine { /// /// 资源下载管理器 /// public class ResourceDownloader : MonoBehaviour { [Header("Configuration")] [Tooltip("资源下载器配置文件")] public ResourceDownloaderConfig config; private string currentVersion = "0.0.0"; private string currentMd5 = ""; private string targetVersion = "0.0.1"; private string targetMd5 = ""; private string persistentDataPath; private string spritesPath; public static ResourceDownloader instance; // 事件 public event Action OnDownloadProgress; public event Action OnDownloadComplete; public event Action OnDownloadError; public event Action OnExtractProgress; public event Action OnExtractComplete; private void Awake() { if (instance == null) { instance = this; DontDestroyOnLoad(gameObject); InitializePaths(); } else { // 如果已经存在实例,销毁这个重复的组件 Debug.LogWarning("ResourceDownloader instance already exists, destroying duplicate"); Destroy(gameObject); } } private void InitializePaths() { persistentDataPath = Application.persistentDataPath; spritesPath = Path.Combine(persistentDataPath, "Sprites"); // 确保目录存在 if (!Directory.Exists(spritesPath)) { Directory.CreateDirectory(spritesPath); } LoadCurrentVersion(); } /// /// 检查是否需要更新资源 /// public async Task CheckForUpdates() { try { Debug.Log($"Starting update check. Current version: {currentVersion}, Current MD5: {currentMd5}"); var serverVersionData = await GetServerVersionData(); if (serverVersionData == null || string.IsNullOrEmpty(serverVersionData.version)) { return false; } targetVersion = serverVersionData.version; targetMd5 = serverVersionData.md5 ?? ""; Debug.Log($"Server version: {targetVersion}, MD5: {targetMd5}, Local version: {currentVersion}, Local MD5: {currentMd5}"); return await CheckVersionAndMd5(); } catch (Exception e) { Debug.LogError($"Error checking for updates: {e.Message}"); return false; } } /// /// 检查版本和MD5,决定是否需要下载 /// private async Task CheckVersionAndMd5() { // 检查版本是否不同 if (currentVersion != targetVersion) { Debug.Log($"Version mismatch. Current: {currentVersion}, Target: {targetVersion}"); return true; } // 检查本地文件是否存在 if (!Directory.Exists(spritesPath) || Directory.GetFiles(spritesPath, "*", SearchOption.AllDirectories).Length == 0) { Debug.Log("Local sprites not found, need to download"); return true; } // 版本相同,检查MD5 if (string.IsNullOrEmpty(targetMd5)) { Debug.Log("No target MD5 provided, assuming files are correct"); return false; } string localMd5 = await CalculateDirectoryMd5(spritesPath); Debug.Log($"版本匹配,开始检查MD5校验... 当前版本: {currentVersion} 目标版本: {targetVersion} 当前MD5: {localMd5} 目标MD5: {targetMd5}"); if (localMd5 != targetMd5) { Debug.Log("MD5校验失败,正在删除损坏的文件并重新下载..."); // 删除损坏的文件 if (Directory.Exists(spritesPath)) { Directory.Delete(spritesPath, true); } return true; // 需要重新下载 } Debug.Log($"✅ MD5校验通过"); return false; // 无需下载 } /// /// 计算目录下所有文件的MD5和 /// private async Task CalculateDirectoryMd5(string directoryPath) { try { if (!Directory.Exists(directoryPath)) return ""; var files = Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories); if (files.Length == 0) return ""; // 按路径排序确保一致性 Array.Sort(files); using (var md5 = System.Security.Cryptography.MD5.Create()) { foreach (string file in files) { // 只处理相对路径部分 string relativePath = file.Substring(directoryPath.Length + 1).Replace('\\', '/'); byte[] pathBytes = System.Text.Encoding.UTF8.GetBytes(relativePath); md5.TransformBlock(pathBytes, 0, pathBytes.Length, null, 0); // 读取文件内容 byte[] fileBytes = File.ReadAllBytes(file); md5.TransformBlock(fileBytes, 0, fileBytes.Length, null, 0); // 每10个文件yield一次,避免阻塞 if (Array.IndexOf(files, file) % 10 == 0) { await Task.Yield(); } } // 完成哈希计算 md5.TransformFinalBlock(new byte[0], 0, 0); string result = BitConverter.ToString(md5.Hash).Replace("-", "").ToLowerInvariant(); return result; } } catch (Exception e) { Debug.LogError($"Error calculating directory MD5: {e.Message}"); return ""; } } /// /// 构建版本化的下载URL /// private string BuildVersionedDownloadUrl(string version) { string targetFileName; if (string.IsNullOrEmpty(version)) { Debug.LogError("Version is null"); return "0.0.0"; } else { targetFileName = version; } if (config == null) { Debug.LogError("ResourceDownloaderConfig is not assigned!"); return "0.0.0"; } string url = config.GetResourceUrl(targetFileName); Debug.Log($"Built download URL: {url} (version: {version}, filename: {targetFileName})"); return url; } /// /// 验证版本化URL是否存在(可选) /// private async Task ValidateVersionedUrl(string url) { if (config == null || !config.validateVersionedUrl) return true; try { using (UnityWebRequest request = UnityWebRequest.Head(url)) { var operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (request.result == UnityWebRequest.Result.Success) { Debug.Log($"Validated versioned URL: {url}"); return true; } else { Debug.LogWarning($"Versioned URL not found: {url} - {request.error}"); return false; } } } catch (Exception e) { Debug.LogError($"Error validating URL {url}: {e.Message}"); return false; } } /// /// 获取服务器版本数据 /// private async Task GetServerVersionData() { if (config == null) { Debug.LogError("ResourceDownloaderConfig is not assigned!"); return null; } string url = config.GetVersionUrl(); Debug.Log($"Version check URL: {url}"); using (UnityWebRequest request = UnityWebRequest.Get(url)) { var operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (request.result == UnityWebRequest.Result.Success) { try { var versionData = JsonUtility.FromJson(request.downloadHandler.text); return versionData; } catch (Exception e) { Debug.LogError($"Failed to parse version response: {e.Message}"); return null; } } else { Debug.LogError($"Failed to get version: {request.error}"); return null; } } } /// /// 下载并更新资源 /// public async Task DownloadAndUpdateResources() { try { string zipFilePath = Path.Combine(persistentDataPath, $"{targetVersion}.zip"); // 构建版本化的下载URL string downloadUrl = BuildVersionedDownloadUrl(targetVersion); // 验证URL是否存在(可选) if (config != null && config.validateVersionedUrl) { bool urlExists = await ValidateVersionedUrl(downloadUrl); if (!urlExists) { OnDownloadError?.Invoke($"Versioned resource not found for version {targetVersion}"); return false; } } // 下载压缩包 bool downloadSuccess = await DownloadFile(downloadUrl, zipFilePath); if (!downloadSuccess) { OnDownloadError?.Invoke("Failed to download resources"); return false; } OnDownloadComplete?.Invoke(zipFilePath); // 解压文件 bool extractSuccess = await ExtractZipFile(zipFilePath, spritesPath); if (!extractSuccess) { OnDownloadError?.Invoke("Failed to extract resources"); return false; } OnExtractComplete?.Invoke(); // 清理下载的压缩包 if (File.Exists(zipFilePath)) { File.Delete(zipFilePath); } // 更新版本信息 SaveCurrentVersion(targetVersion, targetMd5); currentVersion = targetVersion; currentMd5 = targetMd5; Debug.Log($"Resources updated successfully to version {currentVersion}"); return true; } catch (Exception e) { Debug.LogError($"Error downloading/updating resources: {e.Message}"); OnDownloadError?.Invoke(e.Message); return false; } } /// /// 下载文件 /// private async Task DownloadFile(string url, string filePath) { using (UnityWebRequest request = UnityWebRequest.Get(url)) { var operation = request.SendWebRequest(); while (!operation.isDone) { OnDownloadProgress?.Invoke(request.downloadProgress); await Task.Yield(); } if (request.result == UnityWebRequest.Result.Success) { File.WriteAllBytes(filePath, request.downloadHandler.data); return true; } else { Debug.LogError($"Download failed: {request.error}"); return false; } } } /// /// 解压ZIP文件 /// private async Task ExtractZipFile(string zipFilePath, string extractPath) { try { // 清空目标目录 if (Directory.Exists(extractPath)) { Directory.Delete(extractPath, true); } Directory.CreateDirectory(extractPath); using (var archive = ZipFile.OpenRead(zipFilePath)) { int totalEntries = archive.Entries.Count; int processedEntries = 0; foreach (var entry in archive.Entries) { // 跳过目录条目 if (string.IsNullOrEmpty(entry.Name)) continue; string entryPath = Path.Combine(extractPath, entry.FullName); string directoryName = Path.GetDirectoryName(entryPath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } using (var entryStream = entry.Open()) using (var outputStream = File.Create(entryPath)) { await entryStream.CopyToAsync(outputStream); } processedEntries++; OnExtractProgress?.Invoke((float)processedEntries / totalEntries); if (processedEntries % 10 == 0) // 每10个文件yield一次 { await Task.Yield(); } } } return true; } catch (Exception e) { Debug.LogError($"Extract failed: {e.Message}"); return false; } } /// /// 加载当前版本信息 /// private void LoadCurrentVersion() { string versionFile = Path.Combine(persistentDataPath, "version.txt"); if (File.Exists(versionFile)) { string[] lines = File.ReadAllLines(versionFile); if (lines.Length > 0) { currentVersion = lines[0].Trim(); } if (lines.Length > 1) { currentMd5 = lines[1].Trim(); } Debug.Log($"Loaded current version: {currentVersion}, MD5: {currentMd5}"); } else { currentVersion = "0.0.0"; currentMd5 = ""; Debug.Log($"No version file found, using defaults: {currentVersion}"); } } /// /// 保存当前版本信息 /// private void SaveCurrentVersion(string version, string md5 = "") { string versionFile = Path.Combine(persistentDataPath, "version.txt"); string content = version; if (!string.IsNullOrEmpty(md5)) { content += "\n" + md5; } File.WriteAllText(versionFile, content); Debug.Log($"Saved version: {version}, MD5: {md5}"); } /// /// 获取本地Sprite资源路径 /// public string GetLocalSpritePath(string relativePath) { return Path.Combine(spritesPath, relativePath); } /// /// 检查本地资源是否存在 /// public bool HasLocalResources() { return Directory.Exists(spritesPath) && Directory.GetFiles(spritesPath, "*", SearchOption.AllDirectories).Length > 0; } public string GetCurrentVersion() { return currentVersion; } public static ResourceDownloader Get() { if (instance == null) { // 如果没有实例,创建一个新的 GameObject go = new GameObject("ResourceDownloader"); instance = go.AddComponent(); DontDestroyOnLoad(go); // 加载配置文件 instance.LoadConfig(); Debug.Log("Created new ResourceDownloader instance"); } return instance; } /// /// 加载配置文件 /// private void LoadConfig() { // 从Resources文件夹加载配置文件 config = Resources.Load("ResourceDownloaderConfig"); if (config == null) { Debug.LogError("ResourceDownloaderConfig not found in Resources folder! Please create a ResourceDownloaderConfig asset in the Resources folder."); } else { Debug.Log("ResourceDownloaderConfig loaded successfully"); } } } /// /// 资源版本响应数据结构 /// [Serializable] public class ResourceVersionResponse { public string version; public string md5; public string description; public long timestamp; } }