From 7e0dbbb6529bc9b08bd8341fc17b12a5bc02a718 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PB0N82B\\admin" Date: Sat, 15 Nov 2025 15:59:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3d/Assets/Scenes/init.unity | 129 ++++ 3d/Assets/Scenes/init.unity.meta | 7 + 3d/Assets/Scripts/FileIntegrityChecker.cs | 589 ++++++++++++++++++ .../Scripts/FileIntegrityChecker.cs.meta | 11 + 4 files changed, 736 insertions(+) create mode 100644 3d/Assets/Scenes/init.unity create mode 100644 3d/Assets/Scenes/init.unity.meta create mode 100644 3d/Assets/Scripts/FileIntegrityChecker.cs create mode 100644 3d/Assets/Scripts/FileIntegrityChecker.cs.meta diff --git a/3d/Assets/Scenes/init.unity b/3d/Assets/Scenes/init.unity new file mode 100644 index 0000000..7088b6b --- /dev/null +++ b/3d/Assets/Scenes/init.unity @@ -0,0 +1,129 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.12731749, g: 0.13414757, b: 0.1210787, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: [] diff --git a/3d/Assets/Scenes/init.unity.meta b/3d/Assets/Scenes/init.unity.meta new file mode 100644 index 0000000..2df3dd6 --- /dev/null +++ b/3d/Assets/Scenes/init.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c940d0cf1387f98499a1e6ce906edd34 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/3d/Assets/Scripts/FileIntegrityChecker.cs b/3d/Assets/Scripts/FileIntegrityChecker.cs new file mode 100644 index 0000000..98a3fd7 --- /dev/null +++ b/3d/Assets/Scripts/FileIntegrityChecker.cs @@ -0,0 +1,589 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using UnityEngine; +using UnityEngine.SceneManagement; + +/// +/// 文件完整性检查器 +/// 在游戏启动时检查exe目录下的所有exe、dll等文件的MD5值 +/// +public class FileIntegrityChecker +{ + // 需要检查的文件扩展名 + private static readonly string[] TargetExtensions = { ".exe", ".dll", ".so", ".dylib" }; + + // 加密存储的文件名 + private static readonly string ChecksumFileName = "file_checksums.enc"; + + // GUI显示相关的静态变量 + public static string CheckStatusMessage = ""; // 检查状态消息 + public static List MismatchedFilesList = new List(); // 不匹配的文件列表 + public static bool IsFirstRun = false; // 是否是首次运行 + public static int TotalFilesCount = 0; // 总文件数 + + // 加密密钥(基于应用程序路径生成,确保一致性) + private static byte[] GetEncryptionKey() + { + // 使用应用程序路径生成固定密钥 + string appPath = Application.dataPath; + using (SHA256 sha256 = SHA256.Create()) + { + return sha256.ComputeHash(Encoding.UTF8.GetBytes(appPath + "FileIntegrityKey2024")); + } + } + + /// + /// 游戏启动时自动执行文件完整性检查 + /// + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void OnGameStart() + { + try + { + // 获取exe所在目录 + string exeDirectory = GetExeDirectory(); + if (string.IsNullOrEmpty(exeDirectory)) + { + Debug.LogError("[文件完整性检查] 无法获取exe目录路径"); + return; + } + + // 获取streamingAssetsPath目录(用于保存校验文件) + string streamingAssetsPath = Application.streamingAssetsPath; + // 如果streamingAssetsPath目录不存在,创建它 + if (!Directory.Exists(streamingAssetsPath)) + { + Directory.CreateDirectory(streamingAssetsPath); + } + + // 校验文件保存在streamingAssetsPath目录下 + string checksumFilePath = Path.Combine(streamingAssetsPath, ChecksumFileName); + + // 判断是首次运行还是校验模式 + if (!File.Exists(checksumFilePath)) + { + // 首次运行:计算并保存所有文件的MD5(排除streamingAssetsPath目录) + Debug.Log("[文件完整性检查] 首次运行,正在计算文件MD5值..."); + Dictionary fileChecksums = CalculateAllFileChecksums(exeDirectory, streamingAssetsPath); + SaveChecksumsEncrypted(checksumFilePath, fileChecksums); + + // 更新GUI显示信息 + IsFirstRun = true; + TotalFilesCount = fileChecksums.Count; + CheckStatusMessage = $"首次运行完成!共保存 {fileChecksums.Count} 个文件的MD5值"; + MismatchedFilesList.Clear(); + + Debug.Log($"[文件完整性检查] 已完成!共保存 {fileChecksums.Count} 个文件的MD5值"); + } + else + { + // 后续运行:校验文件MD5(排除streamingAssetsPath目录) + Debug.Log("[文件完整性检查] 正在校验文件完整性..."); + Dictionary savedChecksums = LoadChecksumsDecrypted(checksumFilePath); + List mismatchedFiles = VerifyFileChecksums(exeDirectory, savedChecksums, streamingAssetsPath); + + // 更新GUI显示信息 + IsFirstRun = false; + TotalFilesCount = savedChecksums.Count; + MismatchedFilesList = mismatchedFiles; + + if (mismatchedFiles.Count > 0) + { + // 打印所有不匹配的文件 + CheckStatusMessage = $"发现 {mismatchedFiles.Count} 个文件MD5不匹配!"; + Debug.LogError($"[文件完整性检查] 发现 {mismatchedFiles.Count} 个文件MD5不匹配!"); + foreach (string file in mismatchedFiles) + { + Debug.LogError($"[文件完整性检查] MD5不匹配: {file}"); + } + + + } + else + { + CheckStatusMessage = "所有文件完整性校验通过!"; + Debug.Log("[文件完整性检查] 所有文件完整性校验通过!"); + SceneManager.LoadScene("GameLauncher"); + } + } + + // // 创建GUI显示对象 + // CreateGUIDisplay(); + } + catch (Exception ex) + { + Debug.LogError($"[文件完整性检查] 执行过程中发生错误: {ex.Message}\n{ex.StackTrace}"); + } + } + + /// + /// 获取exe所在目录 + /// + private static string GetExeDirectory() + { + // 在Unity编辑器中,Application.dataPath指向Assets文件夹 + // 在构建后的游戏中,Application.dataPath指向"游戏名_Data"文件夹 + string dataPath = Application.dataPath; + + // 获取exe所在目录(dataPath的父目录) + string exeDirectory = Directory.GetParent(dataPath)?.FullName; + + // 输出调试信息 + Debug.Log($"[文件完整性检查] Application.dataPath: {dataPath}"); + Debug.Log($"[文件完整性检查] Exe目录: {exeDirectory}"); + + return exeDirectory; + } + + /// + /// 计算指定目录下所有目标文件的MD5值(递归搜索所有子目录,排除指定目录) + /// + private static Dictionary CalculateAllFileChecksums(string directory, string excludeDirectory) + { + Dictionary checksums = new Dictionary(); + + if (!Directory.Exists(directory)) + { + Debug.LogWarning($"[文件完整性检查] 目录不存在: {directory}"); + return checksums; + } + + // 规范化路径,用于比较 + string normalizedExcludeDir = Path.GetFullPath(excludeDirectory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + Debug.Log($"[文件完整性检查] 开始扫描目录: {directory} (递归搜索所有子目录,排除: {normalizedExcludeDir})"); + + // 递归遍历目录下的所有文件(包括子目录) + string[] allFiles = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + + Debug.Log($"[文件完整性检查] 找到 {allFiles.Length} 个文件,正在筛选目标文件..."); + + foreach (string filePath in allFiles) + { + // 排除streamingAssetsPath目录及其子目录中的文件 + string normalizedFilePath = Path.GetFullPath(filePath); + string normalizedFileDir = Path.GetDirectoryName(normalizedFilePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + // 检查文件是否在排除目录中 + if (normalizedFileDir.StartsWith(normalizedExcludeDir, StringComparison.OrdinalIgnoreCase)) + { + continue; // 跳过排除目录中的文件 + } + + string extension = Path.GetExtension(filePath).ToLower(); + + // 只处理目标扩展名的文件 + if (TargetExtensions.Contains(extension)) + { + try + { + // 获取相对于exe目录的相对路径,用于保存和后续验证 + string relativePath = Path.GetRelativePath(directory, filePath); + string md5 = CalculateFileMD5(filePath); + checksums[relativePath] = md5; + Debug.Log($"[文件完整性检查] 已计算: {relativePath} -> {md5}"); + } + catch (Exception ex) + { + Debug.LogWarning($"[文件完整性检查] 计算文件MD5失败: {filePath}, 错误: {ex.Message}"); + } + } + } + + Debug.Log($"[文件完整性检查] 扫描完成,共找到 {checksums.Count} 个目标文件"); + + return checksums; + } + + /// + /// 计算单个文件的MD5值 + /// + private static string CalculateFileMD5(string filePath) + { + using (MD5 md5 = MD5.Create()) + { + using (FileStream stream = File.OpenRead(filePath)) + { + byte[] hashBytes = md5.ComputeHash(stream); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + } + } + + /// + /// 加密保存MD5校验和字典 + /// + private static void SaveChecksumsEncrypted(string filePath, Dictionary checksums) + { + try + { + // 将字典序列化为JSON格式的字符串 + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.Append("{"); + bool first = true; + foreach (var kvp in checksums) + { + if (!first) jsonBuilder.Append(","); + jsonBuilder.Append($"\"{kvp.Key}\":\"{kvp.Value}\""); + first = false; + } + jsonBuilder.Append("}"); + + string jsonData = jsonBuilder.ToString(); + byte[] dataBytes = Encoding.UTF8.GetBytes(jsonData); + + // 使用AES加密 + byte[] key = GetEncryptionKey(); + byte[] encryptedData = EncryptAES(dataBytes, key); + + // 保存加密后的数据 + File.WriteAllBytes(filePath, encryptedData); + Debug.Log($"[文件完整性检查] MD5数据已加密保存到: {filePath}"); + } + catch (Exception ex) + { + Debug.LogError($"[文件完整性检查] 保存加密数据失败: {ex.Message}"); + throw; + } + } + + /// + /// 解密并加载MD5校验和字典 + /// + private static Dictionary LoadChecksumsDecrypted(string filePath) + { + try + { + // 读取加密数据 + byte[] encryptedData = File.ReadAllBytes(filePath); + + // 解密 + byte[] key = GetEncryptionKey(); + byte[] decryptedData = DecryptAES(encryptedData, key); + + // 解析JSON + string jsonData = Encoding.UTF8.GetString(decryptedData); + Dictionary checksums = new Dictionary(); + + // 简单的JSON解析(因为格式简单,可以手动解析) + jsonData = jsonData.Trim('{', '}'); + if (!string.IsNullOrEmpty(jsonData)) + { + string[] pairs = jsonData.Split(','); + foreach (string pair in pairs) + { + string[] keyValue = pair.Split(':'); + if (keyValue.Length == 2) + { + string fileName = keyValue[0].Trim('"'); + string value = keyValue[1].Trim('"'); + checksums[fileName] = value; + } + } + } + + return checksums; + } + catch (Exception ex) + { + Debug.LogError($"[文件完整性检查] 加载解密数据失败: {ex.Message}"); + return new Dictionary(); + } + } + + /// + /// 校验文件MD5值(递归搜索所有子目录,排除指定目录) + /// + private static List VerifyFileChecksums(string directory, Dictionary savedChecksums, string excludeDirectory) + { + List mismatchedFiles = new List(); + + // 规范化路径,用于比较 + string normalizedExcludeDir = Path.GetFullPath(excludeDirectory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + Debug.Log($"[文件完整性检查] 开始校验文件,已保存 {savedChecksums.Count} 个文件的MD5值(排除目录: {normalizedExcludeDir})"); + + // 检查已保存的文件(使用相对路径) + foreach (var kvp in savedChecksums) + { + string relativePath = kvp.Key; // 相对路径(如 "MonoBleedingEdge/EmbedRuntime/mono.dll") + string savedMD5 = kvp.Value; + string filePath = Path.Combine(directory, relativePath); + + // 检查文件是否在排除目录中 + string normalizedFilePath = Path.GetFullPath(filePath); + string normalizedFileDir = Path.GetDirectoryName(normalizedFilePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + if (normalizedFileDir.StartsWith(normalizedExcludeDir, StringComparison.OrdinalIgnoreCase)) + { + continue; // 跳过排除目录中的文件 + } + + if (!File.Exists(filePath)) + { + // 文件不存在 + mismatchedFiles.Add($"{relativePath} (文件已删除)"); + Debug.LogWarning($"[文件完整性检查] 文件不存在: {relativePath}"); + continue; + } + + try + { + string currentMD5 = CalculateFileMD5(filePath); + if (currentMD5 != savedMD5) + { + // MD5不匹配 + mismatchedFiles.Add(relativePath); + Debug.LogWarning($"[文件完整性检查] MD5不匹配 - 文件: {relativePath}, 保存的MD5: {savedMD5}, 当前的MD5: {currentMD5}"); + } + } + catch (Exception ex) + { + Debug.LogWarning($"[文件完整性检查] 校验文件失败: {relativePath}, 错误: {ex.Message}"); + mismatchedFiles.Add($"{relativePath} (校验失败)"); + } + } + + // 检查是否有新文件(不在保存列表中的目标文件,递归搜索所有子目录,排除指定目录) + Debug.Log($"[文件完整性检查] 检查是否有新增文件..."); + string[] currentFiles = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + foreach (string filePath in currentFiles) + { + // 排除streamingAssetsPath目录及其子目录中的文件 + string normalizedFilePath = Path.GetFullPath(filePath); + string normalizedFileDir = Path.GetDirectoryName(normalizedFilePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + // 检查文件是否在排除目录中 + if (normalizedFileDir.StartsWith(normalizedExcludeDir, StringComparison.OrdinalIgnoreCase)) + { + continue; // 跳过排除目录中的文件 + } + + string extension = Path.GetExtension(filePath).ToLower(); + if (TargetExtensions.Contains(extension)) + { + // 获取相对路径 + string relativePath = Path.GetRelativePath(directory, filePath); + if (!savedChecksums.ContainsKey(relativePath)) + { + mismatchedFiles.Add($"{relativePath} (新增文件,未在记录中)"); + Debug.LogWarning($"[文件完整性检查] 发现新文件: {relativePath}"); + } + } + } + + Debug.Log($"[文件完整性检查] 校验完成,发现 {mismatchedFiles.Count} 个不匹配的文件"); + + return mismatchedFiles; + } + + /// + /// AES加密 + /// + private static byte[] EncryptAES(byte[] data, byte[] key) + { + using (Aes aes = Aes.Create()) + { + // 使用密钥的前32字节作为AES密钥 + byte[] aesKey = new byte[32]; + Array.Copy(key, 0, aesKey, 0, Math.Min(key.Length, 32)); + aes.Key = aesKey; + + // 使用密钥的后16字节作为IV + byte[] iv = new byte[16]; + if (key.Length >= 48) + { + Array.Copy(key, 32, iv, 0, 16); + } + else + { + // 如果密钥不够长,使用前16字节的哈希 + using (MD5 md5 = MD5.Create()) + { + iv = md5.ComputeHash(key).Take(16).ToArray(); + } + } + aes.IV = iv; + + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + using (ICryptoTransform encryptor = aes.CreateEncryptor()) + { + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + } + } + } + } + + /// + /// AES解密 + /// + private static byte[] DecryptAES(byte[] encryptedData, byte[] key) + { + using (Aes aes = Aes.Create()) + { + // 使用密钥的前32字节作为AES密钥 + byte[] aesKey = new byte[32]; + Array.Copy(key, 0, aesKey, 0, Math.Min(key.Length, 32)); + aes.Key = aesKey; + + // 使用密钥的后16字节作为IV + byte[] iv = new byte[16]; + if (key.Length >= 48) + { + Array.Copy(key, 32, iv, 0, 16); + } + else + { + // 如果密钥不够长,使用前16字节的哈希 + using (MD5 md5 = MD5.Create()) + { + iv = md5.ComputeHash(key).Take(16).ToArray(); + } + } + aes.IV = iv; + + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + using (ICryptoTransform decryptor = aes.CreateDecryptor()) + { + using (MemoryStream ms = new MemoryStream(encryptedData)) + { + using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + { + using (MemoryStream result = new MemoryStream()) + { + cs.CopyTo(result); + return result.ToArray(); + } + } + } + } + } + } + + // /// + // /// 创建GUI显示对象 + // /// + // private static void CreateGUIDisplay() + // { + // // 检查是否已存在GUI显示对象 + // GameObject existingGUI = GameObject.Find("FileIntegrityCheckerGUI"); + // if (existingGUI == null) + // { + // // 创建新的GameObject用于显示GUI + // GameObject guiObject = new GameObject("FileIntegrityCheckerGUI"); + // guiObject.AddComponent(); + // // 确保对象不会被销毁 + // UnityEngine.Object.DontDestroyOnLoad(guiObject); + // } + // } +} +// +// /// +// /// 文件完整性检查器GUI显示组件 +// /// 用于在屏幕上显示检查结果 +// /// +// public class FileIntegrityCheckerGUI : MonoBehaviour +// { +// // GUI显示位置和大小 +// private Rect windowRect = new Rect(10, 10, 500, 300); +// private bool showWindow = true; +// private Vector2 scrollPosition = Vector2.zero; +// +// // void OnGUI() +// // { +// // // 绘制窗口 +// // windowRect = GUILayout.Window(1001, windowRect, DrawWindow, "文件完整性检查"); +// // } +// // +// /// +// /// 绘制窗口内容 +// /// +// void DrawWindow(int windowID) +// { +// GUILayout.BeginVertical(); +// +// // 显示检查状态 +// GUIStyle statusStyle = new GUIStyle(GUI.skin.label); +// statusStyle.fontSize = 16; +// statusStyle.fontStyle = FontStyle.Bold; +// +// if (FileIntegrityChecker.IsFirstRun) +// { +// statusStyle.normal.textColor = Color.cyan; +// GUILayout.Label($"状态: {FileIntegrityChecker.CheckStatusMessage}", statusStyle); +// GUILayout.Label($"总文件数: {FileIntegrityChecker.TotalFilesCount}", GUI.skin.label); +// } +// else +// { +// if (FileIntegrityChecker.MismatchedFilesList.Count > 0) +// { +// statusStyle.normal.textColor = Color.red; +// GUILayout.Label($"状态: {FileIntegrityChecker.CheckStatusMessage}", statusStyle); +// } +// else +// { +// statusStyle.normal.textColor = Color.green; +// GUILayout.Label($"状态: {FileIntegrityChecker.CheckStatusMessage}", statusStyle); +// } +// GUILayout.Label($"检查文件数: {FileIntegrityChecker.TotalFilesCount}", GUI.skin.label); +// } +// +// GUILayout.Space(10); +// +// // 显示不匹配的文件列表 +// if (FileIntegrityChecker.MismatchedFilesList.Count > 0) +// { +// GUILayout.Label("不匹配的文件列表:", GUI.skin.label); +// +// // 滚动视图 +// scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(150)); +// +// GUIStyle errorStyle = new GUIStyle(GUI.skin.label); +// errorStyle.normal.textColor = Color.red; +// errorStyle.wordWrap = true; +// +// foreach (string file in FileIntegrityChecker.MismatchedFilesList) +// { +// GUILayout.Label($"❌ {file}", errorStyle); +// } +// +// GUILayout.EndScrollView(); +// } +// else if (!FileIntegrityChecker.IsFirstRun) +// { +// GUIStyle successStyle = new GUIStyle(GUI.skin.label); +// successStyle.normal.textColor = Color.green; +// successStyle.fontSize = 14; +// GUILayout.Label("所有文件完整性正常!", successStyle); +// } +// +// GUILayout.Space(10); +// +// // 关闭按钮 +// if (GUILayout.Button("关闭窗口")) +// { +// showWindow = false; +// Destroy(gameObject); +// } +// +// GUILayout.EndVertical(); +// +// // 允许拖动窗口 +// GUI.DragWindow(); +// } +// } + diff --git a/3d/Assets/Scripts/FileIntegrityChecker.cs.meta b/3d/Assets/Scripts/FileIntegrityChecker.cs.meta new file mode 100644 index 0000000..75faf6b --- /dev/null +++ b/3d/Assets/Scripts/FileIntegrityChecker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4cb828e31751174381de946bb66a890 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: