Compare commits
39 Commits
Patcher2.9
...
main
Author | SHA1 | Date | |
---|---|---|---|
fc6b574f47 | |||
067410e3a2 | |||
291ff54101 | |||
e2c1e9831d | |||
e5f6c681de | |||
![]() |
a22fccd42d | ||
![]() |
38f2425aeb | ||
c49c076ea7 | |||
a8e8b1d221 | |||
21f4a58cc7 | |||
66f1832c21 | |||
32d910473b | |||
da36ea3438 | |||
24fe16177a | |||
5e8293fcbc | |||
e560f85e7e | |||
ecdef3dcec | |||
c272c0da54 | |||
75194f31a5 | |||
854a969e37 | |||
0a481d1e56 | |||
0e97786f13 | |||
2945c5aa0a | |||
f13b49e169 | |||
2e704f30a6 | |||
8cab05567d | |||
b5a8c8ba1c | |||
b31c61ac4e | |||
8d0673e6f4 | |||
d2cb5b6439 | |||
ff7f0f09ef | |||
567e79caf6 | |||
bb730aaa0d | |||
7136dcdf3c | |||
164e2f55f9 | |||
eb2d40547a | |||
91bbdac891 | |||
b6aa4463f4 | |||
8a2a3422bf |
Patcher
.idea/.idea.Patcher/.idea
PatchClient
PatchGenerator
PatcherUtils
13
Patcher/.idea/.idea.Patcher/.idea/.gitignore
generated
vendored
Normal file
13
Patcher/.idea/.idea.Patcher/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/.idea.Patcher.iml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/contentModel.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
12
Patcher/.idea/.idea.Patcher/.idea/avalonia.xml
generated
Normal file
12
Patcher/.idea/.idea.Patcher/.idea/avalonia.xml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AvaloniaProject">
|
||||
<option name="projectPerEditor">
|
||||
<map>
|
||||
<entry key="PatchClient/Views/MainWindow.axaml" value="PatchClient/PatchClient.csproj" />
|
||||
<entry key="PatchGenerator/Views/MainWindow.axaml" value="PatchGenerator/PatchGenerator.csproj" />
|
||||
<entry key="PatchGenerator/Views/OptionsView.axaml" value="PatchGenerator/PatchGenerator.csproj" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
4
Patcher/.idea/.idea.Patcher/.idea/encodings.xml
generated
Normal file
4
Patcher/.idea/.idea.Patcher/.idea/encodings.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
8
Patcher/.idea/.idea.Patcher/.idea/indexLayout.xml
generated
Normal file
8
Patcher/.idea/.idea.Patcher/.idea/indexLayout.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
6
Patcher/.idea/.idea.Patcher/.idea/vcs.xml
generated
Normal file
6
Patcher/.idea/.idea.Patcher/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -3,6 +3,12 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using PatchClient.ViewModels;
|
||||
using PatchClient.Views;
|
||||
using ReactiveUI;
|
||||
using System.Reactive;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using PatcherUtils.Model;
|
||||
|
||||
namespace PatchClient
|
||||
{
|
||||
@ -11,6 +17,11 @@ namespace PatchClient
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>((exception) =>
|
||||
{
|
||||
PatchLogger.LogException(exception);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
@ -18,13 +29,31 @@ namespace PatchClient
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
bool autoClose = false;
|
||||
bool debugOutput = false;
|
||||
|
||||
if(desktop.Args != null && desktop.Args.Length >= 1 && desktop.Args[0]?.ToLower() == "autoclose")
|
||||
autoClose = true;
|
||||
if (desktop.Args != null && desktop.Args.Length >= 1)
|
||||
{
|
||||
autoClose = desktop.Args.Any(x => x.ToLower() == "autoclose");
|
||||
debugOutput = desktop.Args.Any(x => x.ToLower() == "debug");
|
||||
}
|
||||
|
||||
if (debugOutput)
|
||||
{
|
||||
PatchLogger.LogInfo("Running in debug mode");
|
||||
}
|
||||
|
||||
if (autoClose)
|
||||
{
|
||||
PatchLogger.LogInfo("Running with autoclose");
|
||||
}
|
||||
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
PatchLogger.LogInfo($"Patch Client v{version?.ToString() ?? "N/A"}");
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(autoClose),
|
||||
DataContext = new MainWindowViewModel(autoClose, debugOutput),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<AssemblyVersion>2.15.4</AssemblyVersion>
|
||||
<FileVersion>2.15.4</FileVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
|
@ -18,11 +18,11 @@ namespace PatchClient.ViewModels
|
||||
}
|
||||
});
|
||||
|
||||
public MainWindowViewModel(bool autoClose)
|
||||
public MainWindowViewModel(bool autoClose, bool debugOutput)
|
||||
{
|
||||
this.WhenActivated((CompositeDisposable disposable) =>
|
||||
{
|
||||
Router.Navigate.Execute(new PatcherViewModel(this, autoClose));
|
||||
Router.Navigate.Execute(new PatcherViewModel(this, autoClose, debugOutput));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,13 @@ using PatcherUtils;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace PatchClient.ViewModels
|
||||
{
|
||||
@ -16,6 +18,10 @@ namespace PatchClient.ViewModels
|
||||
{
|
||||
private bool _initLineItemProgress = true;
|
||||
private bool _autoClose = false;
|
||||
private bool _debugOutput = false;
|
||||
private Stopwatch _patchStopwatch;
|
||||
private Timer _udpatePatchElapsedTimer = new Timer(1000);
|
||||
|
||||
public ObservableCollection<LineItemProgress> LineItems { get; set; } = new ObservableCollection<LineItemProgress>();
|
||||
|
||||
private string _ProgressMessage = "";
|
||||
@ -39,10 +45,20 @@ namespace PatchClient.ViewModels
|
||||
set => this.RaiseAndSetIfChanged(ref _PatchMessage, value);
|
||||
}
|
||||
|
||||
private string _ElapsedPatchTimeDetails;
|
||||
public string ElapsedPatchTimeDetails
|
||||
{
|
||||
get => _ElapsedPatchTimeDetails;
|
||||
set => this.RaiseAndSetIfChanged(ref _ElapsedPatchTimeDetails, value);
|
||||
}
|
||||
|
||||
public PatcherViewModel(IScreen Host, bool autoClose) : base(Host)
|
||||
|
||||
public PatcherViewModel(IScreen Host, bool autoClose, bool debugOutput) : base(Host)
|
||||
{
|
||||
_autoClose = autoClose;
|
||||
_debugOutput = debugOutput;
|
||||
ElapsedPatchTimeDetails = "Starting ...";
|
||||
_udpatePatchElapsedTimer.Elapsed += _udpatePatchElapsedTimer_Elapsed;
|
||||
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
@ -76,18 +92,32 @@ namespace PatchClient.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private void _udpatePatchElapsedTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
ElapsedPatchTimeDetails = $"Elapsed Patch Time: {_patchStopwatch.Elapsed.ToString("hh':'mm':'ss")}";
|
||||
});
|
||||
}
|
||||
|
||||
private void RunPatcher()
|
||||
{
|
||||
Task.Run(async() =>
|
||||
{
|
||||
LazyOperations.ExtractResourcesToTempDir(Assembly.GetExecutingAssembly());
|
||||
|
||||
PatchHelper patcher = new PatchHelper(Environment.CurrentDirectory, null, LazyOperations.PatchFolder);
|
||||
PatchHelper patcher = new PatchHelper(Environment.CurrentDirectory, null, LazyOperations.PatchFolder, _debugOutput);
|
||||
|
||||
patcher.ProgressChanged += patcher_ProgressChanged;
|
||||
|
||||
_udpatePatchElapsedTimer.Start();
|
||||
_patchStopwatch = Stopwatch.StartNew();
|
||||
|
||||
var patchMessage = patcher.ApplyPatches();
|
||||
|
||||
_patchStopwatch.Stop();
|
||||
_udpatePatchElapsedTimer.Stop();
|
||||
|
||||
LazyOperations.CleanupTempDir();
|
||||
|
||||
Directory.Delete(LazyOperations.PatchFolder, true);
|
||||
|
@ -11,9 +11,6 @@ namespace PatchClient.Views
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
<!-- Current Patch Text -->
|
||||
<Label Content="{Binding ProgressMessage}"/>
|
||||
<Label Content="{Binding ElapsedPatchTimeDetails}" HorizontalAlignment="Right"/>
|
||||
<Label Content="{Binding PatchMessage}" Grid.Row="1"
|
||||
Classes="dark"/>
|
||||
<ProgressBar Grid.Row="2" Value="{Binding PatchPercent}"/>
|
||||
|
@ -4,6 +4,11 @@ using Avalonia.Markup.Xaml;
|
||||
using PatchGenerator.Models;
|
||||
using PatchGenerator.ViewModels;
|
||||
using PatchGenerator.Views;
|
||||
using ReactiveUI;
|
||||
using System.Reactive;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using PatcherUtils.Model;
|
||||
|
||||
namespace PatchGenerator
|
||||
{
|
||||
@ -12,10 +17,20 @@ namespace PatchGenerator
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>((exception) =>
|
||||
{
|
||||
PatchLogger.LogException(exception);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
PatchLogger.LogInfo($"Patch Generator v{version?.ToString() ?? "N/A"}");
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.Startup += Desktop_Startup;
|
||||
|
@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyVersion>2.8</AssemblyVersion>
|
||||
<FileVersion>2.8</FileVersion>
|
||||
<AssemblyVersion>2.15.4</AssemblyVersion>
|
||||
<FileVersion>2.15.4</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -27,8 +27,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\7za.exe" />
|
||||
<EmbeddedResource Include="Resources\PatchClient.exe" />
|
||||
<None Remove="Resources\7z.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.11" />
|
||||
@ -42,4 +41,7 @@
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\PatchClient.exe" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Binary file not shown.
Binary file not shown.
@ -23,7 +23,6 @@ namespace PatchGenerator.ViewModels
|
||||
{
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
|
||||
if (genArgs != null && genArgs.ReadyToRun)
|
||||
{
|
||||
PatchGenInfo genInfo = new PatchGenInfo();
|
||||
@ -31,7 +30,8 @@ namespace PatchGenerator.ViewModels
|
||||
genInfo.TargetFolderPath = genArgs.TargetFolderPath;
|
||||
genInfo.SourceFolderPath = genArgs.SourceFolderPath;
|
||||
genInfo.PatchName = genArgs.OutputFolderName;
|
||||
genInfo.AutoZip = genArgs.AutoZip;
|
||||
// issues with auto zip, but it's not really used anymore so just disabling for now
|
||||
genInfo.AutoZip = false;
|
||||
genInfo.AutoClose = genArgs.AutoClose;
|
||||
|
||||
Router.Navigate.Execute(new PatchGenerationViewModel(this, genInfo));
|
||||
|
@ -14,7 +14,11 @@ using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using PatcherUtils.Model;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace PatchGenerator.ViewModels
|
||||
{
|
||||
@ -48,17 +52,28 @@ namespace PatchGenerator.ViewModels
|
||||
set => this.RaiseAndSetIfChanged(ref _IndeterminateProgress, value);
|
||||
}
|
||||
|
||||
private string _ElapsedTimeDetails;
|
||||
public string ElapsedTimeDetails
|
||||
{
|
||||
get => _ElapsedTimeDetails;
|
||||
set => this.RaiseAndSetIfChanged(ref _ElapsedTimeDetails, value);
|
||||
}
|
||||
|
||||
private LineItem[] lineItems;
|
||||
|
||||
public ObservableCollection<PatchItem> PatchItemCollection { get; set; } = new ObservableCollection<PatchItem>();
|
||||
public ObservableCollection<PatchItem> PatchItemLegendCollection { get; set; } = new ObservableCollection<PatchItem>();
|
||||
|
||||
private Stopwatch patchGenStopwatch = new Stopwatch();
|
||||
private Timer updateElapsedTimeTimer = new Timer(1000);
|
||||
|
||||
private readonly PatchGenInfo generationInfo;
|
||||
public PatchGenerationViewModel(IScreen Host, PatchGenInfo GenerationInfo) : base(Host)
|
||||
{
|
||||
generationInfo = GenerationInfo;
|
||||
ElapsedTimeDetails = "Starting ...";
|
||||
|
||||
updateElapsedTimeTimer.Elapsed += UpdateElapsedTimeTimer_Elapsed;
|
||||
|
||||
foreach (KeyValuePair<string, IBrush> pair in PatchItemDefinitions.Colors)
|
||||
{
|
||||
@ -75,6 +90,14 @@ namespace PatchGenerator.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateElapsedTimeTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
ElapsedTimeDetails = $"Elapsed Patch Time: {patchGenStopwatch.Elapsed.ToString("hh':'mm':'ss")}";
|
||||
});
|
||||
}
|
||||
|
||||
public void GeneratePatches()
|
||||
{
|
||||
Task.Run(() =>
|
||||
@ -87,17 +110,21 @@ namespace PatchGenerator.ViewModels
|
||||
|
||||
patcher.ProgressChanged += Patcher_ProgressChanged;
|
||||
|
||||
updateElapsedTimeTimer.Start();
|
||||
patchGenStopwatch.Start();
|
||||
|
||||
var message = patcher.GeneratePatches();
|
||||
|
||||
patchGenStopwatch.Stop();
|
||||
updateElapsedTimeTimer.Stop();
|
||||
|
||||
if(message.ExitCode != PatcherExitCode.Success && generationInfo.AutoClose)
|
||||
{
|
||||
PatchLogger.LogInfo("Exiting: Auto close on failure");
|
||||
Environment.Exit((int)message.ExitCode);
|
||||
}
|
||||
|
||||
patchGenStopwatch.Stop();
|
||||
|
||||
PatchLogger.LogInfo("Printing summary info ...");
|
||||
PrintSummary();
|
||||
|
||||
StringBuilder sb = new StringBuilder()
|
||||
@ -109,23 +136,33 @@ namespace PatchGenerator.ViewModels
|
||||
ProgressMessage = sb.ToString();
|
||||
|
||||
File.Copy(LazyOperations.PatcherClientPath, $"{generationInfo.PatchName.FromCwd()}\\patcher.exe", true);
|
||||
|
||||
if (generationInfo.AutoZip)
|
||||
{
|
||||
IndeterminateProgress = true;
|
||||
|
||||
PatchItemCollection.Add(new PatchItem("Allowing Time for files to unlock ..."));
|
||||
|
||||
System.Threading.Thread.Sleep(2000);
|
||||
|
||||
PatchItemCollection.Add(new PatchItem("Kicking off 7zip ..."));
|
||||
|
||||
LazyOperations.StartZipProcess(generationInfo.PatchName.FromCwd(), $"{generationInfo.PatchName}.zip".FromCwd());
|
||||
|
||||
IndeterminateProgress = false;
|
||||
|
||||
PatchItemCollection.Add(new PatchItem("Done"));
|
||||
}
|
||||
|
||||
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 ..."));
|
||||
//
|
||||
// Thread.Sleep(2000);
|
||||
//
|
||||
// PatchItemCollection.Add(new PatchItem("Zipping patcher ..."));
|
||||
//
|
||||
// ProgressMessage = "Zipping patcher";
|
||||
//
|
||||
// IndeterminateProgress = false;
|
||||
//
|
||||
// var progress = new Progress<int>(p =>
|
||||
// {
|
||||
// PatchPercent = p;
|
||||
// });
|
||||
//
|
||||
// LazyOperations.CompressDirectory(generationInfo.PatchName.FromCwd(), $"{generationInfo.PatchName}.7z".FromCwd(), progress);
|
||||
//
|
||||
// PatchItemCollection.Add(new PatchItem("Done"));
|
||||
// }
|
||||
|
||||
if (generationInfo.AutoClose)
|
||||
{
|
||||
|
@ -11,9 +11,6 @@ namespace PatchGenerator.Views
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
@ -15,9 +15,10 @@
|
||||
Grid.Row="2" Grid.ColumnSpan="3"
|
||||
Watermark="Output Folder Name"
|
||||
/>
|
||||
|
||||
<CheckBox Content="Zip Generated Files" Grid.Row="4"
|
||||
IsChecked="{Binding GenerationInfo.AutoZip}"/>
|
||||
|
||||
<!-- UNRESOLVED ISSUES: disabling for now -->
|
||||
<!-- <CheckBox Content="Zip Generated Files" Grid.Row="4" -->
|
||||
<!-- IsChecked="{Binding GenerationInfo.AutoZip}"/> -->
|
||||
|
||||
<Button Content="Generate Patches" Grid.ColumnSpan="3" Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
|
@ -64,6 +64,9 @@
|
||||
<Label Content="{Binding ProgressMessage}"/>
|
||||
<Label Content="{Binding PatchPercent, StringFormat={}{0}%}" Grid.Column="2"/>
|
||||
</Grid>
|
||||
|
||||
<Label Content="{Binding ElapsedTimeDetails}" Grid.Row="4" HorizontalAlignment="Left" Margin="10"
|
||||
/>
|
||||
|
||||
<CheckBox Content="AutoScroll" Grid.Row="4" HorizontalAlignment="Right" Margin="10"
|
||||
IsChecked="{Binding AutoScroll}"/>
|
||||
|
161
Patcher/PatcherUtils/Helpers/XdeltaProcessHelper.cs
Normal file
161
Patcher/PatcherUtils/Helpers/XdeltaProcessHelper.cs
Normal file
@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using PatcherUtils.Model;
|
||||
|
||||
namespace PatcherUtils.Helpers;
|
||||
|
||||
public class XdeltaProcessHelper
|
||||
{
|
||||
private readonly int _timeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds;
|
||||
private string _args;
|
||||
private string _sourcePath;
|
||||
private string _deltaPath;
|
||||
private string _decodedPath;
|
||||
private bool _isDebug;
|
||||
|
||||
public XdeltaProcessHelper(string args, string sourcePath, string deltaPath, string decodedPath, bool isDebug)
|
||||
{
|
||||
_args = args;
|
||||
_sourcePath = sourcePath;
|
||||
_deltaPath = deltaPath;
|
||||
_decodedPath = decodedPath;
|
||||
_isDebug = isDebug;
|
||||
}
|
||||
|
||||
public bool Run() => _isDebug ? RunDebug() : RunNormal();
|
||||
|
||||
private bool RunNormal()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var proc = new Process();
|
||||
|
||||
proc.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = LazyOperations.XDelta3Path,
|
||||
Arguments = $"{_args} \"{_sourcePath}\" \"{_deltaPath}\" \"{_decodedPath}\"",
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
proc.Start();
|
||||
|
||||
if (!proc.WaitForExit(_timeout))
|
||||
{
|
||||
PatchLogger.LogError("xdelta3 process timed out");
|
||||
PatchLogger.LogDebug($"xdelta exit code: {proc.ExitCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
PatchLogger.LogDebug($"xdelta exit code: {proc.ExitCode}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool DebugPathsCheck()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = File.Open(_sourcePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
|
||||
stream.Close();
|
||||
stream.Dispose();
|
||||
PatchLogger.LogDebug($"File is openable: {_sourcePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var stream = File.Open(_deltaPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
|
||||
stream.Close();
|
||||
stream.Dispose();
|
||||
PatchLogger.LogDebug($"File is openable: {_deltaPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RunDebug()
|
||||
{
|
||||
if (!DebugPathsCheck())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var proc = new Process();
|
||||
|
||||
proc.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = LazyOperations.XDelta3Path,
|
||||
Arguments = $"{_args} \"{_sourcePath}\" \"{_deltaPath}\" \"{_decodedPath}\"",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
var outputBuilder = new StringBuilder();
|
||||
var errorBuilder = new StringBuilder();
|
||||
|
||||
using AutoResetEvent outputWaitHandle = new AutoResetEvent(false);
|
||||
using AutoResetEvent errorWaitHandle = new AutoResetEvent(false);
|
||||
|
||||
proc.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
outputWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
errorWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
proc.Start();
|
||||
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
|
||||
if (!proc.WaitForExit(_timeout) || !outputWaitHandle.WaitOne(_timeout) || !errorWaitHandle.WaitOne(_timeout))
|
||||
{
|
||||
PatchLogger.LogError("xdelta3 process timed out");
|
||||
PatchLogger.LogDebug($"xdelta exit code: {proc.ExitCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
PatchLogger.LogDebug("__xdelta stdout__");
|
||||
PatchLogger.LogDebug(outputBuilder.ToString());
|
||||
PatchLogger.LogDebug("__xdelta stderr__");
|
||||
PatchLogger.LogDebug(errorBuilder.ToString());
|
||||
PatchLogger.LogDebug($"xdelta exit code: {proc.ExitCode}");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
using PatcherUtils.Model;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using PatcherUtils.Model;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using SevenZip;
|
||||
|
||||
namespace PatcherUtils
|
||||
{
|
||||
@ -16,14 +17,14 @@ namespace PatcherUtils
|
||||
/// <summary>
|
||||
/// The folder that the patches will be stored in
|
||||
/// </summary>
|
||||
public static string PatchFolder = "Aki_Patches";
|
||||
public static string PatchFolder = "SPT_Patches";
|
||||
|
||||
private static string SevenZExe = "7za.exe";
|
||||
private static string SevenZDll = "7z.dll";
|
||||
|
||||
/// <summary>
|
||||
/// The path to the 7za.exe file in the <see cref="TempDir"/>
|
||||
/// </summary>
|
||||
public static string SevenZExePath = $"{TempDir}\\{SevenZExe}";
|
||||
public static string SevenZDllPath = $"{TempDir}\\{SevenZDll}";
|
||||
|
||||
private static string PatcherClient = "PatchClient.exe";
|
||||
/// <summary>
|
||||
@ -79,9 +80,9 @@ namespace PatcherUtils
|
||||
{
|
||||
switch (resource)
|
||||
{
|
||||
case string a when a.EndsWith(SevenZExe):
|
||||
case string a when a.EndsWith(SevenZDll):
|
||||
{
|
||||
StreamResourceOut(assembly, resource, SevenZExePath);
|
||||
StreamResourceOut(assembly, resource, SevenZDllPath);
|
||||
break;
|
||||
}
|
||||
case string a when a.EndsWith(PatcherClient):
|
||||
@ -98,17 +99,47 @@ namespace PatcherUtils
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartZipProcess(string SourcePath, string DestinationPath)
|
||||
public static void CompressDirectory(string SourceDirectoryPath, string DestinationFilePath, IProgress<int> progress)
|
||||
{
|
||||
ProcessStartInfo procInfo = new ProcessStartInfo()
|
||||
try
|
||||
{
|
||||
FileName = SevenZExePath,
|
||||
Arguments = $"a -mm=LZMA {DestinationPath} {SourcePath}"
|
||||
};
|
||||
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,
|
||||
PreserveDirectoryRoot = true
|
||||
};
|
||||
|
||||
Process.Start(procInfo);
|
||||
compressor.Compressing += (_, args) => { progress.Report(args.PercentDone); };
|
||||
|
||||
PatchLogger.LogInfo($"Zip process started");
|
||||
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)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -121,7 +152,7 @@ namespace PatcherUtils
|
||||
if (dir.Exists)
|
||||
{
|
||||
dir.Delete(true);
|
||||
PatchLogger.LogInfo("Temp directory delted");
|
||||
PatchLogger.LogInfo("Temp directory deleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PatcherUtils.Model
|
||||
{
|
||||
@ -23,6 +24,25 @@ namespace PatcherUtils.Model
|
||||
return DateTime.Now.ToString("MM/dd/yyyy - hh:mm:ss tt]");
|
||||
}
|
||||
|
||||
public static void LogOSInfo()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
LogToFile($"{GetTimestamp()}[OS]: Windows");
|
||||
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
LogToFile($"{GetTimestamp()}[OS]: Linux");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
LogToFile($"{GetTimestamp()}[OS]: OSX");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
LogToFile($"{GetTimestamp()}[OS]: FreeBSD");
|
||||
|
||||
|
||||
LogToFile($"{GetTimestamp()}[OS]: {RuntimeInformation.OSDescription}");
|
||||
}
|
||||
|
||||
public static void LogDebug(string message) => LogToFile($"{GetTimestamp()}[DEBUG]: {message}");
|
||||
public static void LogInfo(string message) => LogToFile($"{GetTimestamp()}[INFO]: {message}");
|
||||
public static void LogError(string message) => LogToFile($"{GetTimestamp()}[ERROR]: {message}");
|
||||
public static void LogException(Exception ex) => LogToFile($"{GetTimestamp()}[EXCEPTION]: {ex.Message}\n\nStackTrace:\n{ex.StackTrace}");
|
||||
|
@ -7,6 +7,7 @@
|
||||
EftExeNotFound = 11,
|
||||
NoPatchFolder = 12,
|
||||
MissingFile = 13,
|
||||
MissingDir = 14
|
||||
MissingDir = 14,
|
||||
PatchFailed = 15
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
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 PleOps.XdeltaSharp.Decoder;
|
||||
|
||||
namespace PatcherUtils
|
||||
{
|
||||
@ -23,6 +27,8 @@ namespace PatcherUtils
|
||||
private int delCount;
|
||||
private int existCount;
|
||||
|
||||
private bool debugOutput;
|
||||
|
||||
private List<LineItem> AdditionalInfo = new List<LineItem>();
|
||||
|
||||
/// <summary>
|
||||
@ -31,7 +37,8 @@ namespace PatcherUtils
|
||||
/// <remarks>Includes an array of <see cref="LineItem"/> with details for each type of patch</remarks>
|
||||
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);
|
||||
|
||||
@ -45,11 +52,12 @@ namespace PatcherUtils
|
||||
/// <param name="TargetFolder">The directory to compare against during patch creation.</param>
|
||||
/// <param name="DeltaFolder">The directory where the patches are/will be located.</param>
|
||||
/// <remarks><paramref name="TargetFolder"/> can be null if you only plan to apply patches.</remarks>
|
||||
public PatchHelper(string SourceFolder, string TargetFolder, string DeltaFolder)
|
||||
public PatchHelper(string SourceFolder, string TargetFolder, string DeltaFolder, bool debugOutput = false)
|
||||
{
|
||||
this.SourceFolder = SourceFolder;
|
||||
this.TargetFolder = TargetFolder;
|
||||
this.DeltaFolder = DeltaFolder;
|
||||
this.debugOutput = debugOutput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -75,6 +83,12 @@ namespace PatcherUtils
|
||||
var sourceInfo = new FileInfo(SourceFilePath);
|
||||
var targetInfo = new FileInfo(TargetFilePath);
|
||||
|
||||
// Return false if file size differs
|
||||
if (sourceInfo.Length != targetInfo.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MD5 md5Service = MD5.Create())
|
||||
using (var sourceStream = File.OpenRead(SourceFilePath))
|
||||
using (var targetStream = File.OpenRead(TargetFilePath))
|
||||
@ -84,7 +98,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;
|
||||
}
|
||||
@ -95,35 +110,34 @@ namespace PatcherUtils
|
||||
/// </summary>
|
||||
/// <param name="SourceFilePath"></param>
|
||||
/// <param name="DeltaFilePath"></param>
|
||||
private void ApplyDelta(string SourceFilePath, string DeltaFilePath)
|
||||
private (bool, string) ApplyDelta(string SourceFilePath, string DeltaFilePath)
|
||||
{
|
||||
string decodedPath = SourceFilePath + ".decoded";
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
try
|
||||
{
|
||||
FileName = LazyOperations.XDelta3Path,
|
||||
Arguments = $"-d -f -s \"{SourceFilePath}\" \"{DeltaFilePath}\" \"{decodedPath}\"",
|
||||
CreateNoWindow = true
|
||||
})
|
||||
.WaitForExit();
|
||||
|
||||
if (File.Exists(decodedPath))
|
||||
{
|
||||
PatchLogger.LogInfo($"File delta decoded: {SourceFilePath}");
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(decodedPath, SourceFilePath, true);
|
||||
PatchLogger.LogInfo($"Delta applied: {DeltaFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
using var inputFile = new FileStream(SourceFilePath, FileMode.Open);
|
||||
using var patchFile = new FileStream(DeltaFilePath, FileMode.Open);
|
||||
using var decodedFile = new FileStream(decodedPath, FileMode.Create);
|
||||
using var decoder = new Decoder(inputFile, patchFile, decodedFile);
|
||||
decoder.Run();
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogError($"Failed to decode file delta: {SourceFilePath}");
|
||||
PatchLogger.LogException(ex);
|
||||
return (false, ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(decodedPath, SourceFilePath, true);
|
||||
PatchLogger.LogInfo($"Delta applied: {DeltaFilePath}");
|
||||
return (true, "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,18 +157,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 none -s \"{SourceFilePath}\" \"{TargetFilePath}\" \"{deltaPath}\"",
|
||||
CreateNoWindow = true
|
||||
})
|
||||
.WaitForExit();
|
||||
|
||||
if (File.Exists(deltaPath))
|
||||
{
|
||||
@ -181,7 +195,7 @@ namespace PatcherUtils
|
||||
{
|
||||
Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name + ".del", ""));
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
@ -191,7 +205,7 @@ namespace PatcherUtils
|
||||
File.Create(deltaPath);
|
||||
PatchLogger.LogInfo($"File Created [DEL]: {deltaPath}");
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
@ -212,7 +226,7 @@ namespace PatcherUtils
|
||||
{
|
||||
Directory.CreateDirectory(deltaPath.Replace(targetSourceInfo.Name + ".new", ""));
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
@ -222,7 +236,7 @@ namespace PatcherUtils
|
||||
targetSourceInfo.CopyTo(deltaPath, true);
|
||||
PatchLogger.LogInfo($"File Created [NEW]: {deltaPath}");
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
@ -265,9 +279,11 @@ namespace PatcherUtils
|
||||
|
||||
LazyOperations.ExtractResourcesToTempDir();
|
||||
|
||||
List<FileInfo> SourceFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories).ToList();
|
||||
List<FileInfo> sourceFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories).ToList();
|
||||
var targetFiles = targetDir.GetFiles("*", SearchOption.AllDirectories).ToList();
|
||||
ConcurrentQueue<FileInfo> foundFilesQueue = new ConcurrentQueue<FileInfo>();
|
||||
|
||||
fileCountTotal = SourceFiles.Count;
|
||||
fileCountTotal = targetFiles.Count;
|
||||
|
||||
PatchLogger.LogInfo($"Total source files: {fileCountTotal}");
|
||||
|
||||
@ -281,74 +297,101 @@ namespace PatcherUtils
|
||||
|
||||
RaiseProgressChanged(0, fileCountTotal, "Generating deltas...");
|
||||
|
||||
foreach (FileInfo targetFile in targetDir.GetFiles("*", SearchOption.AllDirectories))
|
||||
try
|
||||
{
|
||||
//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)
|
||||
|
||||
Parallel.ForEach(targetFiles,
|
||||
new ParallelOptions() { MaxDegreeOfParallelism = 5 }, targetFile =>
|
||||
{
|
||||
//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++;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
foreach (var queuedFile in foundFilesQueue)
|
||||
{
|
||||
PatchLogger.LogInfo("::: Creating .new file :::");
|
||||
CreateNewFile(targetFile.FullName);
|
||||
|
||||
newCount++;
|
||||
filesProcessed++;
|
||||
|
||||
RaiseProgressChanged(filesProcessed, fileCountTotal, $"{targetFile.FullName.Replace(TargetFolder, "...")}.new", AdditionalInfo.ToArray());
|
||||
|
||||
continue;
|
||||
RaiseProgressChanged(processedQueueCount, queueTotal, $"Queued file removed: {queuedFile.Name}",
|
||||
AdditionalInfo.ToArray());
|
||||
sourceFiles.Remove(queuedFile);
|
||||
}
|
||||
|
||||
string extension = "";
|
||||
|
||||
//if a matching source file was found, check the file hashes and get the delta.
|
||||
if (!CompareFileHashes(sourceFile.FullName, targetFile.FullName))
|
||||
{
|
||||
PatchLogger.LogInfo("::: Creating .delta file :::");
|
||||
CreateDelta(sourceFile.FullName, targetFile.FullName);
|
||||
extension = ".delta";
|
||||
deltaCount++;
|
||||
}
|
||||
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.
|
||||
//reset progress info
|
||||
|
||||
if (SourceFiles.Count == 0)
|
||||
if (sourceFiles.Count == 0)
|
||||
{
|
||||
PatchLogger.LogInfo("::: Patch Generation Complete :::");
|
||||
|
||||
return new PatchMessage("Generation Done", PatcherExitCode.Success);
|
||||
}
|
||||
|
||||
RaiseProgressChanged(0, SourceFiles.Count, "Processing .del files...");
|
||||
RaiseProgressChanged(0, sourceFiles.Count, "Processing .del files...");
|
||||
filesProcessed = 0;
|
||||
fileCountTotal = SourceFiles.Count;
|
||||
fileCountTotal = sourceFiles.Count;
|
||||
|
||||
foreach (FileInfo delFile in SourceFiles)
|
||||
foreach (FileInfo delFile in sourceFiles)
|
||||
{
|
||||
PatchLogger.LogInfo("::: Creating .del file :::");
|
||||
CreateDelFile(delFile.FullName);
|
||||
@ -358,7 +401,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 :::");
|
||||
@ -374,6 +418,8 @@ namespace PatcherUtils
|
||||
{
|
||||
PatchLogger.LogInfo("::: Starting patch application :::");
|
||||
|
||||
PatchLogger.LogOSInfo();
|
||||
|
||||
//get needed directory information
|
||||
DirectoryInfo sourceDir = new DirectoryInfo(SourceFolder);
|
||||
DirectoryInfo deltaDir = new DirectoryInfo(DeltaFolder);
|
||||
@ -386,7 +432,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);
|
||||
@ -412,83 +458,143 @@ namespace PatcherUtils
|
||||
new LineItem("Files to Delete", delCount)
|
||||
};
|
||||
|
||||
ConcurrentQueue<PatchMessage> errorsQueue = new ConcurrentQueue<PatchMessage>();
|
||||
filesProcessed = 0;
|
||||
|
||||
fileCountTotal = deltaFiles.Count;
|
||||
var patchingTokenSource = new CancellationTokenSource();
|
||||
|
||||
foreach (FileInfo deltaFile in deltaDir.GetFiles("*", SearchOption.AllDirectories))
|
||||
try
|
||||
{
|
||||
switch (deltaFile.Extension)
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
errorsQueue.Enqueue(new PatchMessage(
|
||||
$"Failed to find matching source file for '{deltaFile.FullName}'",
|
||||
PatcherExitCode.MissingFile));
|
||||
patchingTokenSource.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
PatchLogger.LogInfo("::: Applying Delta :::");
|
||||
var result = ApplyDelta(sourceFile.FullName, deltaFile.FullName);
|
||||
|
||||
if (!result.Item1)
|
||||
{
|
||||
errorsQueue.Enqueue(new PatchMessage(result.Item2, PatcherExitCode.PatchFailed));
|
||||
patchingTokenSource.Cancel();
|
||||
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
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destination));
|
||||
File.Copy(deltaFile.FullName, destination, true);
|
||||
PatchLogger.LogInfo($"File added: {destination}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
errorsQueue.Enqueue(new PatchMessage(ex.Message, PatcherExitCode.PatchFailed));
|
||||
patchingTokenSource.Cancel();
|
||||
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
|
||||
{
|
||||
File.Delete(delFilePath);
|
||||
PatchLogger.LogInfo($"File removed: {delFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
errorsQueue.Enqueue(new PatchMessage(ex.Message, PatcherExitCode.PatchFailed));
|
||||
patchingTokenSource.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
delCount--;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AdditionalInfo[0].ItemValue = deltaCount;
|
||||
AdditionalInfo[1].ItemValue = newCount;
|
||||
AdditionalInfo[2].ItemValue = delCount;
|
||||
|
||||
++filesProcessed;
|
||||
RaiseProgressChanged(filesProcessed, fileCountTotal, deltaFile.Name, AdditionalInfo.ToArray());
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
|
||||
if (errorsQueue.Count > 0)
|
||||
{
|
||||
PatchLogger.LogError($"Error queue entry count: {errorsQueue.Count}");
|
||||
PatchLogger.LogError("Dequeuing errors");
|
||||
|
||||
PatchMessage error = null;
|
||||
|
||||
for (int i = 0; i < errorsQueue.Count; i++)
|
||||
{
|
||||
case ".delta":
|
||||
{
|
||||
//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 :::");
|
||||
ApplyDelta(sourceFile.FullName, deltaFile.FullName);
|
||||
|
||||
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
|
||||
{
|
||||
File.Copy(deltaFile.FullName, destination, true);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
File.Delete(delFilePath);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
PatchLogger.LogException(ex);
|
||||
}
|
||||
|
||||
delCount--;
|
||||
|
||||
break;
|
||||
}
|
||||
if (!errorsQueue.TryDequeue(out 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);
|
||||
}
|
||||
|
||||
PatchLogger.LogError(error.Message);
|
||||
}
|
||||
|
||||
AdditionalInfo[0].ItemValue = deltaCount;
|
||||
AdditionalInfo[1].ItemValue = newCount;
|
||||
AdditionalInfo[2].ItemValue = delCount;
|
||||
|
||||
++filesProcessed;
|
||||
RaiseProgressChanged(filesProcessed, fileCountTotal, deltaFile.Name, AdditionalInfo.ToArray());
|
||||
return error ?? new PatchMessage("Something went wrong :(", PatcherExitCode.PatchFailed);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyVersion>2.15.3</AssemblyVersion>
|
||||
<FileVersion>2.15.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -9,6 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\7z.dll" />
|
||||
<EmbeddedResource Include="Resources\xdelta3.exe" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -18,4 +21,9 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PleOps.XdeltaSharp" Version="1.3.0" />
|
||||
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.2.24" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
BIN
Patcher/PatcherUtils/Resources/7z.dll
Normal file
BIN
Patcher/PatcherUtils/Resources/7z.dll
Normal file
Binary file not shown.
@ -40,5 +40,6 @@ public enum PatcherExitCode
|
||||
NoPatchFolder = 12, // no patch folder was found during patching (patch client only)
|
||||
MissingFile = 13, // a matching file could not be found during patching (patch client only)
|
||||
MissingDir = 14 // a directory could not be found during patch generation (source/target/output) (patch generator only)
|
||||
PatchFailed = 15 // a patch file failed (patch client only)
|
||||
}
|
||||
```
|
Loading…
x
Reference in New Issue
Block a user