From 2dbaaa546d5a6270fb3c1e5974b743a999c2746b Mon Sep 17 00:00:00 2001 From: DouDou Date: Thu, 7 Aug 2025 06:43:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0HVM=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=B8=85=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=96=87=E4=BB=B6=E5=BC=BA=E5=88=B6=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CheckDown.hvmprj | 81 ++++++++ Update.cs | 491 ++++++++++++++++++++++++++--------------------- 2 files changed, 350 insertions(+), 222 deletions(-) create mode 100644 CheckDown.hvmprj diff --git a/CheckDown.hvmprj b/CheckDown.hvmprj new file mode 100644 index 0000000..a7f9602 --- /dev/null +++ b/CheckDown.hvmprj @@ -0,0 +1,81 @@ + + + + + .\bin\Debug\CheckDownload.exe + + + + .\bin\Debug_Protected + HVMRuntm.dll + HVMRun64.dll + true + 1 + 0 + true + true + +
CheckDownload.exe
+ 1 + 0 + 0 + + + .\bin\Debug\7z.dll + true + + + .\bin\Debug\CheckDownload.exe.config + true + + + .\bin\Debug\CheckDownload.pdb + true + + + 600 + 400 + 0 + + Loading... + Starting... + + + +
+
+ + true + true + true + true + true + true + true + true + true + true + true + 4 + 3 + 15 + true + true + true + 2 + true + + + true + true + true + 2 + + + + + true + true + + +
diff --git a/Update.cs b/Update.cs index bd92ebe..a2fcc83 100644 --- a/Update.cs +++ b/Update.cs @@ -252,7 +252,7 @@ namespace CheckDownload try { InitializeTempDirectory(); - CleanupNewFiles(); + CleanupOldFiles(); UpdateStatus("下载在线MD5文件并读取..."); UpdateCount(""); UpdateSize(""); @@ -366,27 +366,45 @@ namespace CheckDownload } /// - /// 清理旧的更新文件,删除所有.new后缀的临时文件 + /// 清理旧的更新文件,删除备份文件和临时文件 /// - private void CleanupNewFiles() + private void CleanupOldFiles() { 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("正在清理旧的更新文件..."); - foreach (var file in newFiles) + UpdateStatus("正在清理备份文件..."); + foreach (var file in backupFiles) { try { - File.Delete(file); + // 只清理超过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) + { + try + { + File.Delete(file); + } + catch + { + } + } } catch { @@ -914,7 +932,7 @@ namespace CheckDownload } /// - /// 验证所有下载文件的MD5完整性并保存到目标位置,处理文件占用情况 + /// 验证所有下载文件的MD5完整性并保存到目标位置,强制替换占用的文件 /// private async Task VerifyAndSaveAllFiles() { @@ -922,11 +940,7 @@ namespace CheckDownload UpdateCount(""); UpdateSize(""); - // 新增:在开始校验前创建临时文件备份 - //await CreateTempFileBackup(); - var failedFiles = new ConcurrentBag(); - var filesForScripting = new ConcurrentBag<(string original, string newFile)>(); int totalFiles = _downloadedFiles.Count; int completed = 0; @@ -941,18 +955,17 @@ namespace CheckDownload string expectedMd5 = item.Value; string tempFilePath = Path.Combine(_tempDirectory, relativePath); - // 添加调试信息:检查临时文件是否存在 + // 检查临时文件是否存在 if (!File.Exists(tempFilePath)) { - //UpdateStatus($"[调试] 临时文件在校验前消失: {relativePath}"); failedFiles.Add(relativePath); return; } + // 验证MD5 string actualMd5 = CalculateMD5FromFile(tempFilePath); if (actualMd5 != expectedMd5.ToLower()) { - //UpdateStatus($"[调试] 文件MD5校验失败: {relativePath}"); failedFiles.Add(relativePath); return; } @@ -964,23 +977,11 @@ namespace CheckDownload Directory.CreateDirectory(localDir); } - //UpdateStatus($"[调试] 开始复制文件: {relativePath}"); - if (!await TryCopyFileAsync(tempFilePath, localPath)) // 改为使用复制方法 + // 使用强制替换方法 + if (!await ForceReplaceFileAsync(tempFilePath, localPath)) { - //UpdateStatus($"[调试] 直接复制失败,创建.new文件: {relativePath}"); - string backupPath = localPath + ".new"; - File.Copy(tempFilePath, backupPath, true); // 复制而非移动 - filesForScripting.Add((localPath, backupPath)); - } - else - { - //UpdateStatus($"[调试] 文件复制成功: {relativePath}"); - } - - // 复制后再次检查临时文件是否还存在 - if (!File.Exists(tempFilePath)) - { - //UpdateStatus($"[调试] 警告:文件复制后临时文件消失: {relativePath}"); + failedFiles.Add(relativePath); + return; } } finally @@ -994,11 +995,6 @@ namespace CheckDownload await Task.WhenAll(tasks); - if (filesForScripting.Any()) - { - CreateReplaceScriptForAll(filesForScripting.ToList()); - } - foreach (var failed in failedFiles) { _downloadedFiles.Remove(failed); @@ -1019,178 +1015,9 @@ namespace CheckDownload } } - /// - /// 尝试将文件从临时位置移动到目标位置(异步),若文件被占用则等待一秒后重试一次 - /// - /// 源文件的完整路径 - /// 目标文件的完整路径 - /// 移动成功返回true,失败返回false - private async Task 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; - } - } - /// - /// 为被占用文件创建批处理脚本,在程序退出后自动完成文件替换 - /// - /// 包含原始文件路径和新文件路径的元组列表 - 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); - } - } /// /// 初始化程序使用的临时目录,用于存储下载过程中的临时文件 @@ -1864,18 +1691,64 @@ namespace CheckDownload } /// - /// 尝试将文件从源位置复制到目标位置 + /// 强制替换文件,检查占用进程并强制杀掉 /// - private async Task TryCopyFileAsync(string sourcePath, string targetPath) + /// 源文件路径 + /// 目标文件路径 + /// 成功返回true,失败返回false + private async Task 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; + } + } + + /// + /// 尝试直接替换文件 + /// + private async Task TryDirectReplaceAsync(string sourcePath, string targetPath) { try { - // 如果目标文件是.exe文件,先检查并kill掉可能正在运行的进程 - if (Path.GetExtension(targetPath).Equals(".exe", StringComparison.OrdinalIgnoreCase)) - { - KillProcessIfRunning(targetPath); - } - // 确保目标目录存在 string targetDir = Path.GetDirectoryName(targetPath); if (!Directory.Exists(targetDir)) @@ -1883,25 +1756,199 @@ namespace CheckDownload Directory.CreateDirectory(targetDir); } - // 执行复制操作 + // 直接复制覆盖 File.Copy(sourcePath, targetPath, true); return true; } - catch (Exception ex) + catch { - // 文件可能被占用,稍后重试 + return false; + } + } + + /// + /// 强制杀掉占用指定文件的进程 + /// + 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 + { + // 忽略杀进程过程中的错误 + } + } + + /// + /// 强制杀掉可能使用指定DLL的进程 + /// + 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 + { + // 忽略错误 + } + } + + /// + /// 使用系统工具强制解锁并替换文件 + /// + private async Task 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 { - await Task.Delay(1000); - File.Copy(sourcePath, targetPath, true); - return true; + if (File.Exists(backupPath)) + { + File.Delete(backupPath); + } } catch { - UpdateStatus($"文件复制失败: {Path.GetFileName(targetPath)}"); - return false; + // 忽略备份文件删除失败 } + + return true; } + catch + { + return false; + } + } + + /// + /// 尝试将文件从源位置复制到目标位置(保留原方法用于兼容性) + /// + private async Task TryCopyFileAsync(string sourcePath, string targetPath) + { + return await ForceReplaceFileAsync(sourcePath, targetPath); } ///