Test-TaizhouWarehousePhaseII/3d/Assets/Scripts/FileIntegrityChecker.cs

590 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// 文件完整性检查器
/// 在游戏启动时检查exe目录下的所有exe、dll等文件的MD5值
/// </summary>
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<string> MismatchedFilesList = new List<string>(); // 不匹配的文件列表
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"));
}
}
/// <summary>
/// 游戏启动时自动执行文件完整性检查
/// </summary>
[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<string, string> 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<string, string> savedChecksums = LoadChecksumsDecrypted(checksumFilePath);
List<string> 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}");
}
}
/// <summary>
/// 获取exe所在目录
/// </summary>
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;
}
/// <summary>
/// 计算指定目录下所有目标文件的MD5值递归搜索所有子目录排除指定目录
/// </summary>
private static Dictionary<string, string> CalculateAllFileChecksums(string directory, string excludeDirectory)
{
Dictionary<string, string> checksums = new Dictionary<string, string>();
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;
}
/// <summary>
/// 计算单个文件的MD5值
/// </summary>
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();
}
}
}
/// <summary>
/// 加密保存MD5校验和字典
/// </summary>
private static void SaveChecksumsEncrypted(string filePath, Dictionary<string, string> 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;
}
}
/// <summary>
/// 解密并加载MD5校验和字典
/// </summary>
private static Dictionary<string, string> 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<string, string> checksums = new Dictionary<string, string>();
// 简单的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<string, string>();
}
}
/// <summary>
/// 校验文件MD5值递归搜索所有子目录排除指定目录
/// </summary>
private static List<string> VerifyFileChecksums(string directory, Dictionary<string, string> savedChecksums, string excludeDirectory)
{
List<string> mismatchedFiles = new List<string>();
// 规范化路径,用于比较
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;
}
/// <summary>
/// AES加密
/// </summary>
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();
}
}
}
}
}
/// <summary>
/// AES解密
/// </summary>
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();
}
}
}
}
}
}
// /// <summary>
// /// 创建GUI显示对象
// /// </summary>
// private static void CreateGUIDisplay()
// {
// // 检查是否已存在GUI显示对象
// GameObject existingGUI = GameObject.Find("FileIntegrityCheckerGUI");
// if (existingGUI == null)
// {
// // 创建新的GameObject用于显示GUI
// GameObject guiObject = new GameObject("FileIntegrityCheckerGUI");
// guiObject.AddComponent<FileIntegrityCheckerGUI>();
// // 确保对象不会被销毁
// UnityEngine.Object.DontDestroyOnLoad(guiObject);
// }
// }
}
//
// /// <summary>
// /// 文件完整性检查器GUI显示组件
// /// 用于在屏幕上显示检查结果
// /// </summary>
// 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, "文件完整性检查");
// // }
// //
// /// <summary>
// /// 绘制窗口内容
// /// </summary>
// 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();
// }
// }