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: