添加HVM项目配置文件,更新文件清理逻辑,增强文件强制替换功能

This commit is contained in:
2025-08-07 06:43:34 +08:00
parent 334e30b257
commit 2dbaaa546d
2 changed files with 350 additions and 222 deletions

81
CheckDown.hvmprj Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<HVMProject version ="2">
<Assemblies>
<assembly>
<path>.\bin\Debug\CheckDownload.exe</path>
</assembly>
</Assemblies>
<Output>
<path>.\bin\Debug_Protected</path>
<runtime>HVMRuntm.dll</runtime>
<runtime64>HVMRun64.dll</runtime64>
<X64Optimize>true</X64Optimize>
<LibMode>1</LibMode>
<EmbedRuntime>0</EmbedRuntime>
<shrink>true</shrink>
<SingleEXE>true</SingleEXE>
<EXE>
<Main>CheckDownload.exe</Main>
<PackType>1</PackType>
<EntryType>0</EntryType>
<LoadType>0</LoadType>
<Assemblies>
<assembly>
<path>.\bin\Debug\7z.dll</path>
<datafile>true</datafile>
</assembly>
<assembly>
<path>.\bin\Debug\CheckDownload.exe.config</path>
<datafile>true</datafile>
</assembly>
<assembly>
<path>.\bin\Debug\CheckDownload.pdb</path>
<datafile>true</datafile>
</assembly>
<SplashConfig>
<Width>600</Width>
<Height>400</Height>
<iAutoClose>0</iAutoClose>
<Image></Image>
<msg1>Loading...</msg1>
<msg2>Starting...</msg2>
<title></title>
</SplashConfig>
</Assemblies>
</EXE>
</Output>
<Settings>
<EncryptComplierGen>true</EncryptComplierGen>
<AntiDump>true</AntiDump>
<EncryptNew>true</EncryptNew>
<EncryptRes>true</EncryptRes>
<EncryptBlob>true</EncryptBlob>
<EncryptUS>true</EncryptUS>
<HVM>true</HVM>
<HVM2>true</HVM2>
<HVMSig>true</HVMSig>
<HVMEH>true</HVMEH>
<HVMStr>true</HVMStr>
<Level>4</Level>
<Level2>3</Level2>
<CompatLevel>15</CompatLevel>
<AntiTrace>true</AntiTrace>
<HVMAntiTrace>true</HVMAntiTrace>
<HVMProxyMethod>true</HVMProxyMethod>
<AnonymousProtection>2</AnonymousProtection>
<StripConstantsString>true</StripConstantsString>
</Settings>
<Obfuscation>
<AutoRename>true</AutoRename>
<ObILCode>true</ObILCode>
<ObMeta>true</ObMeta>
<RenameMode>2</RenameMode>
</Obfuscation>
<TrialSettings>
</TrialSettings>
<LicenseSettings>
<AddBiosID>true</AddBiosID>
<AddMacID>true</AddMacID>
<MasterKey></MasterKey>
</LicenseSettings>
</HVMProject>

495
Update.cs
View File

@@ -252,7 +252,7 @@ namespace CheckDownload
try try
{ {
InitializeTempDirectory(); InitializeTempDirectory();
CleanupNewFiles(); CleanupOldFiles();
UpdateStatus("下载在线MD5文件并读取..."); UpdateStatus("下载在线MD5文件并读取...");
UpdateCount(""); UpdateCount("");
UpdateSize(""); UpdateSize("");
@@ -366,16 +366,35 @@ namespace CheckDownload
} }
/// <summary> /// <summary>
/// 清理旧的更新文件,删除所有.new后缀的临时文件 /// 清理旧的更新文件,删除备份文件和临时文件
/// </summary> /// </summary>
private void CleanupNewFiles() private void CleanupOldFiles()
{ {
try try
{ {
var newFiles = Directory.GetFiles(_baseDirectory, "*.new", SearchOption.AllDirectories); // 清理备份文件
if (newFiles.Length > 0) var backupFiles = Directory.GetFiles(_baseDirectory, "*.bak_*", SearchOption.AllDirectories);
if (backupFiles.Length > 0)
{ {
UpdateStatus("正在清理旧的更新文件..."); UpdateStatus("正在清理备份文件...");
foreach (var file in backupFiles)
{
try
{
// 只清理超过1小时的备份文件
if (File.GetCreationTime(file) < DateTime.Now.AddHours(-1))
{
File.Delete(file);
}
}
catch
{
}
}
}
// 清理旧的 .new 文件(如果还有遗留的)
var newFiles = Directory.GetFiles(_baseDirectory, "*.new", SearchOption.AllDirectories);
foreach (var file in newFiles) foreach (var file in newFiles)
{ {
try try
@@ -387,7 +406,6 @@ namespace CheckDownload
} }
} }
} }
}
catch catch
{ {
} }
@@ -914,7 +932,7 @@ namespace CheckDownload
} }
/// <summary> /// <summary>
/// 验证所有下载文件的MD5完整性并保存到目标位置处理文件占用情况 /// 验证所有下载文件的MD5完整性并保存到目标位置强制替换占用的文件
/// </summary> /// </summary>
private async Task VerifyAndSaveAllFiles() private async Task VerifyAndSaveAllFiles()
{ {
@@ -922,11 +940,7 @@ namespace CheckDownload
UpdateCount(""); UpdateCount("");
UpdateSize(""); UpdateSize("");
// 新增:在开始校验前创建临时文件备份
//await CreateTempFileBackup();
var failedFiles = new ConcurrentBag<string>(); var failedFiles = new ConcurrentBag<string>();
var filesForScripting = new ConcurrentBag<(string original, string newFile)>();
int totalFiles = _downloadedFiles.Count; int totalFiles = _downloadedFiles.Count;
int completed = 0; int completed = 0;
@@ -941,18 +955,17 @@ namespace CheckDownload
string expectedMd5 = item.Value; string expectedMd5 = item.Value;
string tempFilePath = Path.Combine(_tempDirectory, relativePath); string tempFilePath = Path.Combine(_tempDirectory, relativePath);
// 添加调试信息:检查临时文件是否存在 // 检查临时文件是否存在
if (!File.Exists(tempFilePath)) if (!File.Exists(tempFilePath))
{ {
//UpdateStatus($"[调试] 临时文件在校验前消失: {relativePath}");
failedFiles.Add(relativePath); failedFiles.Add(relativePath);
return; return;
} }
// 验证MD5
string actualMd5 = CalculateMD5FromFile(tempFilePath); string actualMd5 = CalculateMD5FromFile(tempFilePath);
if (actualMd5 != expectedMd5.ToLower()) if (actualMd5 != expectedMd5.ToLower())
{ {
//UpdateStatus($"[调试] 文件MD5校验失败: {relativePath}");
failedFiles.Add(relativePath); failedFiles.Add(relativePath);
return; return;
} }
@@ -964,23 +977,11 @@ namespace CheckDownload
Directory.CreateDirectory(localDir); Directory.CreateDirectory(localDir);
} }
//UpdateStatus($"[调试] 开始复制文件: {relativePath}"); // 使用强制替换方法
if (!await TryCopyFileAsync(tempFilePath, localPath)) // 改为使用复制方法 if (!await ForceReplaceFileAsync(tempFilePath, localPath))
{ {
//UpdateStatus($"[调试] 直接复制失败,创建.new文件: {relativePath}"); failedFiles.Add(relativePath);
string backupPath = localPath + ".new"; return;
File.Copy(tempFilePath, backupPath, true); // 复制而非移动
filesForScripting.Add((localPath, backupPath));
}
else
{
//UpdateStatus($"[调试] 文件复制成功: {relativePath}");
}
// 复制后再次检查临时文件是否还存在
if (!File.Exists(tempFilePath))
{
//UpdateStatus($"[调试] 警告:文件复制后临时文件消失: {relativePath}");
} }
} }
finally finally
@@ -994,11 +995,6 @@ namespace CheckDownload
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
if (filesForScripting.Any())
{
CreateReplaceScriptForAll(filesForScripting.ToList());
}
foreach (var failed in failedFiles) foreach (var failed in failedFiles)
{ {
_downloadedFiles.Remove(failed); _downloadedFiles.Remove(failed);
@@ -1019,178 +1015,9 @@ namespace CheckDownload
} }
} }
/// <summary>
/// 尝试将文件从临时位置移动到目标位置(异步),若文件被占用则等待一秒后重试一次
/// </summary>
/// <param name="sourcePath">源文件的完整路径</param>
/// <param name="targetPath">目标文件的完整路径</param>
/// <returns>移动成功返回true失败返回false</returns>
private async Task<bool> TryMoveFileAsync(string sourcePath, string targetPath)
{
try
{
File.Move(sourcePath, targetPath);
return true;
}
catch (IOException)
{
UpdateStatus($"文件被占用,尝试解锁...");
try
{
await Task.Delay(1000);
File.Move(sourcePath, targetPath);
UpdateStatus($"文件解锁成功并已更新");
return true;
}
catch
{
UpdateStatus($"文件仍被占用,无法移动");
return false;
}
}
catch (Exception ex)
{
UpdateStatus($"移动文件时发生错误: {ex.Message}");
return false;
}
}
/// <summary>
/// 为被占用文件创建批处理脚本,在程序退出后自动完成文件替换
/// </summary>
/// <param name="files">包含原始文件路径和新文件路径的元组列表</param>
private void CreateReplaceScriptForAll(List<(string original, string newFile)> files)
{
if (!files.Any()) return;
try
{
string batchFilePath = Path.Combine(_baseDirectory, "update_files.bat");
string processId = Process.GetCurrentProcess().Id.ToString();
string processName = Process.GetCurrentProcess().ProcessName;
// 创建锁文件,程序退出前会删除它
File.WriteAllText(_updateLockFilePath, processId);
var batchContent = new StringBuilder();
batchContent.AppendLine("@echo off");
batchContent.AppendLine("title Update Process");
// 简化的等待逻辑,避免复杂的计数器
batchContent.AppendLine("echo 等待程序退出...");
batchContent.AppendLine(":wait_loop");
// 优先检查锁文件
batchContent.AppendLine($"if not exist \"{_updateLockFilePath}\" goto process_check");
// 等待0.5秒
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 500 >nul");
batchContent.AppendLine("goto wait_loop");
batchContent.AppendLine(":process_check");
// 再次确认进程已退出
batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>nul | find /I \"{processId}\" >nul");
batchContent.AppendLine("if %ERRORLEVEL% equ 0 (");
batchContent.AppendLine(" ping 127.0.0.1 -n 1 -w 500 >nul");
batchContent.AppendLine(" goto process_check");
batchContent.AppendLine(")");
// 额外等待确保完全退出
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 1000 >nul");
batchContent.AppendLine("echo 开始更新文件...");
// 简化的文件处理逻辑,避免复杂的标签
int fileIndex = 0;
foreach (var file in files)
{
string fileName = Path.GetFileName(file.original);
batchContent.AppendLine($"echo 处理文件: {fileName}");
// 检查新文件是否存在
batchContent.AppendLine($"if not exist \"{file.newFile}\" (");
batchContent.AppendLine($" echo 错误: 新文件不存在 - {fileName}");
batchContent.AppendLine($" goto file_{fileIndex}_end");
batchContent.AppendLine($")");
// 删除原文件(如果存在)
batchContent.AppendLine($"if exist \"{file.original}\" (");
batchContent.AppendLine($" echo 删除原文件: {fileName}");
batchContent.AppendLine($" del \"{file.original}\" /f /q");
batchContent.AppendLine($")");
// 复制新文件
batchContent.AppendLine($"echo 复制新文件: {fileName}");
batchContent.AppendLine($"copy /Y \"{file.newFile}\" \"{file.original}\"");
// 验证复制是否成功
batchContent.AppendLine($"if exist \"{file.original}\" (");
batchContent.AppendLine($" echo 成功更新: {fileName}");
batchContent.AppendLine($" del \"{file.newFile}\" /f /q");
batchContent.AppendLine($") else (");
batchContent.AppendLine($" echo 失败: 无法创建 {fileName}");
batchContent.AppendLine($")");
batchContent.AppendLine($":file_{fileIndex}_end");
fileIndex++;
}
// 清理工作
batchContent.AppendLine("echo 清理临时文件...");
// 删除锁文件
batchContent.AppendLine($"if exist \"{_updateLockFilePath}\" del \"{_updateLockFilePath}\" /f /q");
// 简化的清理逻辑
batchContent.AppendLine($"cd /d \"{_baseDirectory}\"");
batchContent.AppendLine("for %%f in (*.new) do del \"%%f\" /f /q");
batchContent.AppendLine("for %%f in (*.bak) do del \"%%f\" /f /q");
batchContent.AppendLine("echo 更新完成");
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 2000 >nul");
// 删除批处理脚本自身
batchContent.AppendLine("del \"%~f0\" /f /q");
File.WriteAllText(batchFilePath, batchContent.ToString(), new UTF8Encoding(false));
// 使用Win7兼容的启动方式
var startInfo = new ProcessStartInfo
{
FileName = batchFilePath,
CreateNoWindow = false, // Win7下设为false更稳定
UseShellExecute = true, // Win7下必须使用Shell执行
WindowStyle = ProcessWindowStyle.Minimized,
WorkingDirectory = _baseDirectory
};
try
{
Process.Start(startInfo);
}
catch (Exception)
{
// 如果上面的方式失败尝试直接用cmd执行
var fallbackStartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c \"{batchFilePath}\"",
CreateNoWindow = true,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = _baseDirectory
};
Process.Start(fallbackStartInfo);
}
UpdateStatus("已创建更新脚本,程序退出后将自动完成更新");
}
catch (Exception ex)
{
HandleError("创建文件替换脚本 (CreateReplaceScriptForAll)", ex);
}
}
/// <summary> /// <summary>
/// 初始化程序使用的临时目录,用于存储下载过程中的临时文件 /// 初始化程序使用的临时目录,用于存储下载过程中的临时文件
@@ -1864,18 +1691,64 @@ namespace CheckDownload
} }
/// <summary> /// <summary>
/// 尝试将文件从源位置复制到目标位置 /// 强制替换文件,检查占用进程并强制杀掉
/// </summary> /// </summary>
private async Task<bool> TryCopyFileAsync(string sourcePath, string targetPath) /// <param name="sourcePath">源文件路径</param>
/// <param name="targetPath">目标文件路径</param>
/// <returns>成功返回true失败返回false</returns>
private async Task<bool> ForceReplaceFileAsync(string sourcePath, string targetPath)
{
string fileName = Path.GetFileName(targetPath);
try
{
// 第一次尝试直接替换
if (await TryDirectReplaceAsync(sourcePath, targetPath))
{
return true;
}
// 如果失败,检查并强制杀掉占用的进程
UpdateStatus($"文件被占用,正在强制终止相关进程: {fileName}");
// 强制杀掉可能占用文件的进程
await ForceKillProcessesUsingFileAsync(targetPath);
// 等待进程完全退出
await Task.Delay(2000);
// 再次尝试替换
if (await TryDirectReplaceAsync(sourcePath, targetPath))
{
UpdateStatus($"强制替换成功: {fileName}");
return true;
}
// 如果还是失败,尝试使用系统工具强制解锁
UpdateStatus($"尝试解锁文件: {fileName}");
if (await ForceUnlockAndReplaceAsync(sourcePath, targetPath))
{
UpdateStatus($"解锁并替换成功: {fileName}");
return true;
}
UpdateStatus($"强制替换失败: {fileName}");
return false;
}
catch (Exception ex)
{
UpdateStatus($"强制替换异常: {fileName} - {ex.Message}");
return false;
}
}
/// <summary>
/// 尝试直接替换文件
/// </summary>
private async Task<bool> TryDirectReplaceAsync(string sourcePath, string targetPath)
{ {
try try
{ {
// 如果目标文件是.exe文件先检查并kill掉可能正在运行的进程
if (Path.GetExtension(targetPath).Equals(".exe", StringComparison.OrdinalIgnoreCase))
{
KillProcessIfRunning(targetPath);
}
// 确保目标目录存在 // 确保目标目录存在
string targetDir = Path.GetDirectoryName(targetPath); string targetDir = Path.GetDirectoryName(targetPath);
if (!Directory.Exists(targetDir)) if (!Directory.Exists(targetDir))
@@ -1883,25 +1756,199 @@ namespace CheckDownload
Directory.CreateDirectory(targetDir); Directory.CreateDirectory(targetDir);
} }
// 执行复制操作 // 直接复制覆盖
File.Copy(sourcePath, targetPath, true);
return true;
}
catch (Exception ex)
{
// 文件可能被占用,稍后重试
try
{
await Task.Delay(1000);
File.Copy(sourcePath, targetPath, true); File.Copy(sourcePath, targetPath, true);
return true; return true;
} }
catch catch
{ {
UpdateStatus($"文件复制失败: {Path.GetFileName(targetPath)}");
return false; return false;
} }
} }
/// <summary>
/// 强制杀掉占用指定文件的进程
/// </summary>
private async Task ForceKillProcessesUsingFileAsync(string filePath)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(filePath);
string fileDirectory = Path.GetDirectoryName(filePath);
// 1. 杀掉同名的进程
await Task.Run(() =>
{
var processes = Process.GetProcessesByName(fileName);
foreach (var proc in processes)
{
if (proc.Id == _currentProcessId) continue;
try
{
// 检查进程是否在同一目录下
string procPath = proc.MainModule?.FileName;
if (!string.IsNullOrEmpty(procPath))
{
string procDir = Path.GetDirectoryName(procPath);
if (string.Equals(procDir, fileDirectory, StringComparison.OrdinalIgnoreCase))
{
UpdateStatus($"强制终止进程: {proc.ProcessName} (PID: {proc.Id})");
proc.Kill();
proc.WaitForExit(3000);
}
}
}
catch
{
// 忽略无法访问的进程
}
}
});
// 2. 使用 taskkill 强制终止
await Task.Run(() =>
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = "taskkill",
Arguments = $"/F /IM {fileName}.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var process = Process.Start(startInfo))
{
process?.WaitForExit(5000);
}
}
catch
{
// 忽略 taskkill 失败
}
});
// 3. 如果是 DLL 文件,尝试卸载相关进程
if (Path.GetExtension(filePath).Equals(".dll", StringComparison.OrdinalIgnoreCase))
{
await ForceKillProcessesUsingDllAsync(filePath);
}
}
catch
{
// 忽略杀进程过程中的错误
}
}
/// <summary>
/// 强制杀掉可能使用指定DLL的进程
/// </summary>
private async Task ForceKillProcessesUsingDllAsync(string dllPath)
{
try
{
string dllName = Path.GetFileName(dllPath);
string baseDir = _baseDirectory.ToLowerInvariant();
await Task.Run(() =>
{
var allProcesses = Process.GetProcesses();
foreach (var proc in allProcesses)
{
if (proc.Id == _currentProcessId) continue;
try
{
string procPath = proc.MainModule?.FileName;
if (!string.IsNullOrEmpty(procPath) &&
procPath.ToLowerInvariant().StartsWith(baseDir))
{
UpdateStatus($"终止可能使用DLL的进程: {proc.ProcessName} (PID: {proc.Id})");
proc.Kill();
proc.WaitForExit(3000);
}
}
catch
{
// 忽略无法访问的进程
}
}
});
}
catch
{
// 忽略错误
}
}
/// <summary>
/// 使用系统工具强制解锁并替换文件
/// </summary>
private async Task<bool> ForceUnlockAndReplaceAsync(string sourcePath, string targetPath)
{
try
{
// 尝试使用 handle.exe 或其他系统工具来解锁文件
// 这里先尝试简单的重命名删除方法
string backupPath = targetPath + ".bak_" + DateTime.Now.Ticks;
// 尝试重命名原文件
if (File.Exists(targetPath))
{
try
{
File.Move(targetPath, backupPath);
}
catch
{
// 如果重命名失败,尝试设置文件属性
try
{
File.SetAttributes(targetPath, FileAttributes.Normal);
await Task.Delay(500);
File.Move(targetPath, backupPath);
}
catch
{
return false;
}
}
}
// 复制新文件
File.Copy(sourcePath, targetPath, true);
// 删除备份文件
try
{
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
}
catch
{
// 忽略备份文件删除失败
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 尝试将文件从源位置复制到目标位置(保留原方法用于兼容性)
/// </summary>
private async Task<bool> TryCopyFileAsync(string sourcePath, string targetPath)
{
return await ForceReplaceFileAsync(sourcePath, targetPath);
} }
/// <summary> /// <summary>