diff --git a/Patcher/PatchClient/PatchClient.csproj b/Patcher/PatchClient/PatchClient.csproj index ed2bdbf..5a22d0c 100644 --- a/Patcher/PatchClient/PatchClient.csproj +++ b/Patcher/PatchClient/PatchClient.csproj @@ -4,8 +4,8 @@ net8.0 true enable - 2.12.0 - 2.12.0 + 2.15.0 + 2.15.0 diff --git a/Patcher/PatchGenerator/PatchGenerator.csproj b/Patcher/PatchGenerator/PatchGenerator.csproj index f70c910..f24e52d 100644 --- a/Patcher/PatchGenerator/PatchGenerator.csproj +++ b/Patcher/PatchGenerator/PatchGenerator.csproj @@ -4,8 +4,8 @@ net8.0 true enable - 2.12.0 - 2.12.0 + 2.15.0 + 2.15.0 diff --git a/Patcher/PatchGenerator/Resources/PatchClient.exe b/Patcher/PatchGenerator/Resources/PatchClient.exe index 2407816..6acb46a 100644 --- a/Patcher/PatchGenerator/Resources/PatchClient.exe +++ b/Patcher/PatchGenerator/Resources/PatchClient.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f6df2d05c2ec1d33446146dd61eebc2c238411c04e02fae71fa1f13d2926b4f +oid sha256:741f62f01cd9e2d86d463a01d69d03d78020b806882de4fe0f5e56870b261355 size 96953730 diff --git a/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs b/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs index 1d83b55..fd1a41c 100644 --- a/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs +++ b/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs @@ -112,15 +112,17 @@ namespace PatchGenerator.ViewModels patchGenStopwatch.Start(); var message = patcher.GeneratePatches(); - - if(message.ExitCode != PatcherExitCode.Success && generationInfo.AutoClose) - { - Environment.Exit((int)message.ExitCode); - } - + patchGenStopwatch.Stop(); updateElapsedTimeTimer.Stop(); + if(message.ExitCode != PatcherExitCode.Success && generationInfo.AutoClose) + { + PatchLogger.LogInfo("Exiting: Auto close on failure"); + Environment.Exit((int)message.ExitCode); + } + + PatchLogger.LogInfo("Printing summary info ..."); PrintSummary(); StringBuilder sb = new StringBuilder() @@ -132,9 +134,12 @@ namespace PatchGenerator.ViewModels ProgressMessage = sb.ToString(); File.Copy(LazyOperations.PatcherClientPath, $"{generationInfo.PatchName.FromCwd()}\\patcher.exe", true); - + + PatchLogger.LogInfo("Copied patcher.exe to output folder"); + if (generationInfo.AutoZip) { + PatchLogger.LogInfo("AutoZipping"); IndeterminateProgress = true; PatchItemCollection.Add(new PatchItem("Allowing Time for files to unlock ...")); @@ -149,6 +154,7 @@ namespace PatchGenerator.ViewModels var progress = new Progress(p => { + PatchLogger.LogInfo($"compressing directory @ {p}%"); PatchPercent = p; }); diff --git a/Patcher/PatcherUtils/LazyOperations.cs b/Patcher/PatcherUtils/LazyOperations.cs index 8c059bf..e35c7ac 100644 --- a/Patcher/PatcherUtils/LazyOperations.cs +++ b/Patcher/PatcherUtils/LazyOperations.cs @@ -102,31 +102,43 @@ namespace PatcherUtils public static void CompressDirectory(string SourceDirectoryPath, string DestinationFilePath, IProgress progress) { - var outputFile = new FileInfo(DestinationFilePath); - - SevenZipBase.SetLibraryPath(SevenZDllPath); - var compressor = new SevenZipCompressor() + try { - ArchiveFormat = OutArchiveFormat.SevenZip, - CompressionMethod = CompressionMethod.Lzma2, - CompressionLevel = CompressionLevel.Normal - }; + PatchLogger.LogInfo($"Compressing: {SourceDirectoryPath}"); + PatchLogger.LogInfo($"Output file: {DestinationFilePath}"); + var outputFile = new FileInfo(DestinationFilePath); + SevenZipBase.SetLibraryPath(SevenZDllPath); + + PatchLogger.LogInfo($"7z.dll set: {SevenZDllPath}"); + + var compressor = new SevenZipCompressor() + { + ArchiveFormat = OutArchiveFormat.SevenZip, + CompressionMethod = CompressionMethod.Lzma2, + CompressionLevel = CompressionLevel.Normal + }; - compressor.Compressing += (_, args) => + compressor.Compressing += (_, args) => { progress.Report(args.PercentDone); }; + + using var outputStream = outputFile.OpenWrite(); + + PatchLogger.LogInfo("Starting compression"); + + compressor.CompressDirectory(SourceDirectoryPath, outputStream); + + PatchLogger.LogInfo("Compression complete"); + + outputFile.Refresh(); + + // failed to compress data + if (!outputFile.Exists || outputFile.Length == 0) + { + PatchLogger.LogError("Failed to compress patcher"); + } + } + catch (Exception ex) { - progress.Report(args.PercentDone); - }; - - using var outputStream = outputFile.OpenWrite(); - - compressor.CompressDirectory(SourceDirectoryPath, outputStream); - - outputFile.Refresh(); - - // failed to compress data - if (!outputFile.Exists || outputFile.Length == 0) - { - Logger.LogError("Failed to compress patcher"); + PatchLogger.LogException(ex); } } @@ -140,7 +152,7 @@ namespace PatcherUtils if (dir.Exists) { dir.Delete(true); - PatchLogger.LogInfo("Temp directory delted"); + PatchLogger.LogInfo("Temp directory deleted"); } } } diff --git a/Patcher/PatcherUtils/PatchHelper.cs b/Patcher/PatcherUtils/PatchHelper.cs index 6710e21..debb749 100644 --- a/Patcher/PatcherUtils/PatchHelper.cs +++ b/Patcher/PatcherUtils/PatchHelper.cs @@ -1,11 +1,14 @@ using PatchClient.Models; using PatcherUtils.Model; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; using PatcherUtils.Helpers; namespace PatcherUtils @@ -34,7 +37,8 @@ namespace PatcherUtils /// Includes an array of with details for each type of patch public event ProgressChangedHandler ProgressChanged; - protected virtual void RaiseProgressChanged(int progress, int total, string Message = "", params LineItem[] AdditionalLineItems) + protected virtual void RaiseProgressChanged(int progress, int total, string Message = "", + params LineItem[] AdditionalLineItems) { int percent = (int)Math.Floor((double)progress / total * 100); @@ -88,7 +92,8 @@ namespace PatcherUtils bool matched = Enumerable.SequenceEqual(sourceHash, targetHash); - PatchLogger.LogInfo($"Hash Check: S({sourceInfo.Name}|{Convert.ToBase64String(sourceHash)}) - T({targetInfo.Name}|{Convert.ToBase64String(targetHash)}) - Match:{matched}"); + PatchLogger.LogInfo( + $"Hash Check: S({sourceInfo.Name}|{Convert.ToBase64String(sourceHash)}) - T({targetInfo.Name}|{Convert.ToBase64String(targetHash)}) - Match:{matched}"); return matched; } @@ -153,18 +158,18 @@ namespace PatcherUtils { Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name + ".delta", "")); } - catch(Exception ex) + catch (Exception ex) { PatchLogger.LogException(ex); } Process.Start(new ProcessStartInfo - { - FileName = LazyOperations.XDelta3Path, - Arguments = $"-0 -e -f -s \"{SourceFilePath}\" \"{TargetFilePath}\" \"{deltaPath}\"", - CreateNoWindow = true - }) - .WaitForExit(); + { + FileName = LazyOperations.XDelta3Path, + Arguments = $"-0 -e -f -s \"{SourceFilePath}\" \"{TargetFilePath}\" \"{deltaPath}\"", + CreateNoWindow = true + }) + .WaitForExit(); if (File.Exists(deltaPath)) { @@ -191,7 +196,7 @@ namespace PatcherUtils { Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name + ".del", "")); } - catch(Exception ex) + catch (Exception ex) { PatchLogger.LogException(ex); } @@ -201,7 +206,7 @@ namespace PatcherUtils File.Create(deltaPath); PatchLogger.LogInfo($"File Created [DEL]: {deltaPath}"); } - catch(Exception ex) + catch (Exception ex) { PatchLogger.LogException(ex); } @@ -222,7 +227,7 @@ namespace PatcherUtils { Directory.CreateDirectory(deltaPath.Replace(targetSourceInfo.Name + ".new", "")); } - catch(Exception ex) + catch (Exception ex) { PatchLogger.LogException(ex); } @@ -232,7 +237,7 @@ namespace PatcherUtils targetSourceInfo.CopyTo(deltaPath, true); PatchLogger.LogInfo($"File Created [NEW]: {deltaPath}"); } - catch(Exception ex) + catch (Exception ex) { PatchLogger.LogException(ex); } @@ -276,6 +281,7 @@ namespace PatcherUtils LazyOperations.ExtractResourcesToTempDir(); List SourceFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories).ToList(); + ConcurrentQueue foundFilesQueue = new ConcurrentQueue(); fileCountTotal = SourceFiles.Count; @@ -290,58 +296,79 @@ namespace PatcherUtils filesProcessed = 0; RaiseProgressChanged(0, fileCountTotal, "Generating deltas..."); - - foreach (FileInfo targetFile in targetDir.GetFiles("*", SearchOption.AllDirectories)) - { - //find a matching source file based on the relative path of the file - FileInfo sourceFile = SourceFiles.Find(f => f.FullName.Replace(sourceDir.FullName, "") == targetFile.FullName.Replace(targetDir.FullName, "")); - - //if the target file doesn't exist in the source files, the target file needs to be added. - if (sourceFile == null) + + // use 5 threads to process source files / create deltas + Parallel.ForEach(targetDir.GetFiles("*", SearchOption.AllDirectories), + new ParallelOptions() { MaxDegreeOfParallelism = 5 }, + targetFile => { - PatchLogger.LogInfo("::: Creating .new file :::"); - CreateNewFile(targetFile.FullName); + //find a matching source file based on the relative path of the file + + FileInfo sourceFile = SourceFiles.Find(f => + f.FullName.Replace(sourceDir.FullName, "") == + targetFile.FullName.Replace(targetDir.FullName, "")); + + //if the target file doesn't exist in the source files, the target file needs to be added. + if (sourceFile == null) + { + PatchLogger.LogInfo("::: Creating .new file :::"); + CreateNewFile(targetFile.FullName); + + newCount++; + filesProcessed++; + + RaiseProgressChanged(filesProcessed, fileCountTotal, + $"{targetFile.FullName.Replace(TargetFolder, "...")}.new", AdditionalInfo.ToArray()); + + return; + } + + string extension = ""; + + // if a matching source file was found, check the file hashes and get the delta. + // add it to the bag for removal later + if (!CompareFileHashes(sourceFile.FullName, targetFile.FullName)) + { + foundFilesQueue.Enqueue(sourceFile); + PatchLogger.LogInfo("::: Creating .delta file :::"); + CreateDelta(sourceFile.FullName, targetFile.FullName); + extension = ".delta"; + deltaCount++; + } + else + { + foundFilesQueue.Enqueue(sourceFile); + PatchLogger.LogInfo("::: File Exists :::"); + existCount++; + } - newCount++; filesProcessed++; - RaiseProgressChanged(filesProcessed, fileCountTotal, $"{targetFile.FullName.Replace(TargetFolder, "...")}.new", AdditionalInfo.ToArray()); + AdditionalInfo[0].ItemValue = deltaCount; + AdditionalInfo[1].ItemValue = newCount; + AdditionalInfo[3].ItemValue = existCount; - continue; - } + RaiseProgressChanged(filesProcessed, fileCountTotal, + $"{targetFile.FullName.Replace(TargetFolder, "...")}{extension}", AdditionalInfo.ToArray()); + }); - string extension = ""; + // remove all queued files that were found in the source files list + PatchLogger.LogInfo(":: Updating Source List ::"); + try + { + int processedQueueCount = 0; + int queueTotal = foundFilesQueue.Count; - //if a matching source file was found, check the file hashes and get the delta. - if (!CompareFileHashes(sourceFile.FullName, targetFile.FullName)) + foreach (var queuedFile in foundFilesQueue) { - PatchLogger.LogInfo("::: Creating .delta file :::"); - CreateDelta(sourceFile.FullName, targetFile.FullName); - extension = ".delta"; - deltaCount++; + RaiseProgressChanged(processedQueueCount, queueTotal, $"Queued file removed: {queuedFile.Name}", + AdditionalInfo.ToArray()); + SourceFiles.Remove(queuedFile); } - else - { - PatchLogger.LogInfo("::: File Exists :::"); - existCount++; - } - - try - { - SourceFiles.Remove(sourceFile); - } - catch(Exception ex) - { - PatchLogger.LogException(ex); - } - - filesProcessed++; - - AdditionalInfo[0].ItemValue = deltaCount; - AdditionalInfo[1].ItemValue = newCount; - AdditionalInfo[3].ItemValue = existCount; - - RaiseProgressChanged(filesProcessed, fileCountTotal, $"{targetFile.FullName.Replace(TargetFolder, "...")}{extension}", AdditionalInfo.ToArray()); + } + catch (Exception ex) + { + PatchLogger.LogException(ex); } //Any remaining source files do not exist in the target folder and can be removed. @@ -368,7 +395,8 @@ namespace PatcherUtils AdditionalInfo[2].ItemValue = delCount; filesProcessed++; - RaiseProgressChanged(filesProcessed, fileCountTotal, $"{delFile.FullName.Replace(SourceFolder, "...")}.del", AdditionalInfo.ToArray()); + RaiseProgressChanged(filesProcessed, fileCountTotal, + $"{delFile.FullName.Replace(SourceFolder, "...")}.del", AdditionalInfo.ToArray()); } PatchLogger.LogInfo("::: Patch Generation Complete :::"); @@ -398,7 +426,7 @@ namespace PatcherUtils return new PatchMessage(message, PatcherExitCode.MissingDir); } - if(!deltaDir.Exists) + if (!deltaDir.Exists) { string message = $"Could not find delta directory: {deltaDir.FullName}"; PatchLogger.LogError(message); @@ -424,81 +452,100 @@ namespace PatcherUtils new LineItem("Files to Delete", delCount) }; + ConcurrentQueue errorsQueue = new ConcurrentQueue(); filesProcessed = 0; fileCountTotal = deltaFiles.Count; + var patchingTokenSource = new CancellationTokenSource(); - foreach (FileInfo deltaFile in deltaDir.GetFiles("*", SearchOption.AllDirectories)) + // foreach (FileInfo deltaFile in deltaDir.GetFiles("*", SearchOption.AllDirectories)) + Parallel.ForEach(deltaDir.GetFiles("*", SearchOption.AllDirectories).ToList(), + new ParallelOptions() { MaxDegreeOfParallelism = 5, CancellationToken = patchingTokenSource.Token}, + deltaFile => { switch (deltaFile.Extension) { case ".delta": + { + //apply delta + FileInfo sourceFile = SourceFiles.Find(f => + f.FullName.Replace(sourceDir.FullName, "") == deltaFile.FullName + .Replace(deltaDir.FullName, "").Replace(".delta", "")); + + if (sourceFile == null) { - //apply delta - FileInfo sourceFile = SourceFiles.Find(f => f.FullName.Replace(sourceDir.FullName, "") == deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".delta", "")); - - if (sourceFile == null) - { - return new PatchMessage($"Failed to find matching source file for '{deltaFile.FullName}'", PatcherExitCode.MissingFile); - } - - PatchLogger.LogInfo("::: Applying Delta :::"); - var result = ApplyDelta(sourceFile.FullName, deltaFile.FullName); - - if(!result.Item1) - { - return new PatchMessage(result.Item2, PatcherExitCode.PatchFailed); - } - - deltaCount--; - - break; + patchingTokenSource.Cancel(); + errorsQueue.Enqueue(new PatchMessage( + $"Failed to find matching source file for '{deltaFile.FullName}'", + PatcherExitCode.MissingFile)); + return; } + + PatchLogger.LogInfo("::: Applying Delta :::"); + var result = ApplyDelta(sourceFile.FullName, deltaFile.FullName); + + if (!result.Item1) + { + patchingTokenSource.Cancel(); + errorsQueue.Enqueue(new PatchMessage(result.Item2, PatcherExitCode.PatchFailed)); + return; + } + + deltaCount--; + + break; + } case ".new": + { + //copy new file + string destination = Path.Join(sourceDir.FullName, + deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".new", "")); + + PatchLogger.LogInfo("::: Adding New File :::"); + + try { - //copy new file - string destination = Path.Join(sourceDir.FullName, deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".new", "")); - - PatchLogger.LogInfo("::: Adding New File :::"); - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(destination)); - File.Copy(deltaFile.FullName, destination, true); - PatchLogger.LogInfo($"File added: {destination}"); - } - catch(Exception ex) - { - PatchLogger.LogException(ex); - return new PatchMessage(ex.Message, PatcherExitCode.PatchFailed); - } - - newCount--; - - break; + Directory.CreateDirectory(Path.GetDirectoryName(destination)); + File.Copy(deltaFile.FullName, destination, true); + PatchLogger.LogInfo($"File added: {destination}"); } + catch (Exception ex) + { + patchingTokenSource.Cancel(); + PatchLogger.LogException(ex); + errorsQueue.Enqueue(new PatchMessage(ex.Message, PatcherExitCode.PatchFailed)); + return; + } + + newCount--; + + break; + } case ".del": + { + //remove unneeded file + string delFilePath = Path.Join(sourceDir.FullName, + deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".del", "")); + + PatchLogger.LogInfo("::: Removing Uneeded File :::"); + + try { - //remove unneeded file - string delFilePath = Path.Join(sourceDir.FullName, deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".del", "")); - - PatchLogger.LogInfo("::: Removing Uneeded File :::"); - - try - { - File.Delete(delFilePath); - PatchLogger.LogInfo($"File removed: {delFilePath}"); - } - catch(Exception ex) - { - PatchLogger.LogException(ex); - return new PatchMessage(ex.Message, PatcherExitCode.PatchFailed); - } - - delCount--; - - break; + File.Delete(delFilePath); + PatchLogger.LogInfo($"File removed: {delFilePath}"); } + catch (Exception ex) + { + patchingTokenSource.Cancel(); + PatchLogger.LogException(ex); + errorsQueue.Enqueue(new PatchMessage(ex.Message, PatcherExitCode.PatchFailed)); + return; + } + + delCount--; + + break; + } } AdditionalInfo[0].ItemValue = deltaCount; @@ -507,10 +554,21 @@ namespace PatcherUtils ++filesProcessed; RaiseProgressChanged(filesProcessed, fileCountTotal, deltaFile.Name, AdditionalInfo.ToArray()); + }); + + if (errorsQueue.Count > 0) + { + if (!errorsQueue.TryDequeue(out PatchMessage error)) + { + return new PatchMessage("Errors occurred during patching, but we couldn't dequeue them :(\n\nThere may be more information in the log", PatcherExitCode.PatchFailed); + } + + return error; } PatchLogger.LogInfo("::: Patching Complete :::"); - return new PatchMessage($"Patching Complete. You can delete the patcher.exe file.", PatcherExitCode.Success); + return new PatchMessage($"Patching Complete. You can delete the patcher.exe file.", + PatcherExitCode.Success); } } -} +} \ No newline at end of file