文件校验
This commit is contained in:
parent
124d1cdc48
commit
7e0dbbb652
|
|
@ -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: []
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c940d0cf1387f98499a1e6ce906edd34
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4cb828e31751174381de946bb66a890
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Loading…
Reference in New Issue