fuck idk like all kinds of stuff...

This commit is contained in:
IsWaffle 2021-08-01 00:36:37 -04:00
parent b3a4eaad55
commit fefec3e779
18 changed files with 833 additions and 0 deletions

31
Patcher/Patcher.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31515.178
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PatchGenerator", "Patcher\PatchGenerator.csproj", "{DDB70566-994E-4884-8555-C005B238039B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PatcherUtils", "PatcherUtils\PatcherUtils.csproj", "{A9819B34-8111-4344-B2B3-3DE5D7A43A45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DDB70566-994E-4884-8555-C005B238039B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDB70566-994E-4884-8555-C005B238039B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDB70566-994E-4884-8555-C005B238039B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDB70566-994E-4884-8555-C005B238039B}.Release|Any CPU.Build.0 = Release|Any CPU
{A9819B34-8111-4344-B2B3-3DE5D7A43A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9819B34-8111-4344-B2B3-3DE5D7A43A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9819B34-8111-4344-B2B3-3DE5D7A43A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9819B34-8111-4344-B2B3-3DE5D7A43A45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9AA1062-33C6-49F2-880C-067D44041C4A}
EndGlobalSection
EndGlobal

9
Patcher/Patcher/App.xaml Normal file
View File

@ -0,0 +1,9 @@
<Application x:Class="PatchGenerator.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatchGenerator"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace PatchGenerator
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,71 @@
<Window x:Class="PatchGenerator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PatchGenerator"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="*"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<ProgressBar x:Name="GenProgressBar"
Grid.ColumnSpan="2"
Height="20" Margin="10"
Foreground="MediumPurple"
/>
<Label x:Name="GenProgressMessageLabel"
FontSize="15" FontWeight="SemiBold"
Grid.ColumnSpan="2" Margin="0 0 10 0"
HorizontalAlignment="Right" VerticalAlignment="Center"
/>
<Label x:Name="GenProgressInfoLabel"
FontSize="15" FontWeight="SemiBold"
Grid.ColumnSpan="2" Margin="10 0 0 0"
HorizontalAlignment="Left" VerticalAlignment="Center"
/>
<Label x:Name="CompareLabel"
Grid.Row="1" Margin="10 10 5 10"
Drop="CompareLabel_Drop"
AllowDrop="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="Drag and drop COMPARE folder here"
BorderBrush="Gainsboro" BorderThickness="2"
/>
<Label x:Name="TargetLabel"
Grid.Column="1" Grid.Row="1" Margin="5 10 10 10"
Drop="TargetLabel_Drop"
AllowDrop="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="Drag and drop TARGET folder here"
BorderBrush="Gainsboro" BorderThickness="2"
/>
<TextBlock x:Name="AdditionalInfoBlock"
Grid.Row="2" Margin="10"
TextWrapping="Wrap"
/>
<Button x:Name="GenButton"
Grid.Column="1" Grid.Row="2"
MinHeight="40" Margin="10"
Content="Generate Patches"
Click="GenButton_Click"
/>
</Grid>
</Window>

View File

@ -0,0 +1,173 @@
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using PatcherUtils;
namespace PatchGenerator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string compareFolder = "";
private string targetFolder = "";
private readonly string patchFolder = "Aki_Data/Patcher/".FromCwd();
private Stopwatch stopwatch = new Stopwatch();
public MainWindow()
{
InitializeComponent();
}
private string GetStopWatchTime()
{
return $"Hours: {stopwatch.Elapsed.Hours} - Mins: {stopwatch.Elapsed.Minutes} - Secs: {stopwatch.Elapsed.Seconds} - MilliSecs: {stopwatch.Elapsed.Milliseconds}";
}
private static bool FileDropCheck(DragEventArgs args, ref string str)
{
if (!args.Data.GetDataPresent(DataFormats.FileDrop))
{
return false;
}
string[] paths = (string[])args.Data.GetData(DataFormats.FileDrop);
if (paths.Length != 1) return false;
if (!Directory.Exists(paths[0]))
{
return false;
}
str = paths[0];
return true;
}
private void CompareLabel_Drop(object sender, DragEventArgs e)
{
if (FileDropCheck(e, ref compareFolder))
{
CompareLabel.Content = $"Compare Folder:\n{compareFolder}";
CompareLabel.BorderBrush = Brushes.DarkCyan;
}
else
{
MessageBox.Show("Dropped File/s could not be used. Make sure you only drop one folder.");
}
}
private void TargetLabel_Drop(object sender, DragEventArgs e)
{
if(FileDropCheck(e, ref targetFolder))
{
TargetLabel.Content = $"Target Folder:\n{targetFolder}";
TargetLabel.BorderBrush = Brushes.DarkCyan;
}
else
{
MessageBox.Show("Dropped File/s could not be used. Make sure you only drop one folder.");
}
}
private void GeneratePatches()
{
//create temp data
Application.Current.Dispatcher.Invoke(() =>
{
GenProgressBar.IsIndeterminate = true;
GenProgressMessageLabel.Content = "Extracting temp data ...";
});
LazyOperations.PrepTempDir();
Application.Current.Dispatcher.Invoke(() =>
{
GenProgressBar.IsIndeterminate = false;
});
//generate patches
FileCompare bc = new FileCompare(targetFolder, compareFolder, patchFolder);
bc.ProgressChanged += Bc_ProgressChanged;
if (!bc.CompareAll())
{
MessageBox.Show("Failed to generate diffs.", ":(", MessageBoxButton.OK, MessageBoxImage.Error);
}
//TODO - Build patcher
//TODO - compress to file (should add a name textbox or something)
//Cleanup temp data
Application.Current.Dispatcher.Invoke(() =>
{
GenProgressBar.Value = 100;
GenProgressMessageLabel.Content = $"Done";
});
if (!LazyOperations.CleanupTempDir())
{
MessageBox.Show($"Looks like some temp files could not be removed. You can safely delete this folder:\n\n{LazyOperations.TempDir}");
}
}
private void Bc_ProgressChanged(object Sender, int Progress, int Total, int Percent, string Message = "", params LineItem[] AdditionalLineItems)
{
string additionalInfo = "";
foreach(LineItem item in AdditionalLineItems)
{
additionalInfo += $"{item.ItemText}: {item.ItemValue}\n";
}
Application.Current.Dispatcher.Invoke(() =>
{
GenProgressBar.Value = Percent;
if (!string.IsNullOrWhiteSpace(Message))
{
GenProgressMessageLabel.Content = Message;
}
GenProgressInfoLabel.Content = $"[{Progress}/{Total}]";
AdditionalInfoBlock.Text = additionalInfo;
});
}
private void GenButton_Click(object sender, RoutedEventArgs e)
{
GenButton.IsEnabled = false;
Task.Run(() =>
{
stopwatch.Reset();
stopwatch.Start();
try
{
GeneratePatches();
}
finally
{
stopwatch.Stop();
Application.Current.Dispatcher.Invoke(() =>
{
GenButton.IsEnabled = true;
GenProgressMessageLabel.Content = "";
GenProgressInfoLabel.Content = $"Patches Generated in: {GetStopWatchTime()}";
});
}
});
}
}
}

View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\7za.exe" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\7za.exe" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PatcherUtils\PatcherUtils.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Aki.ByteBanger">
<HintPath>References\Aki.ByteBanger.dll</HintPath>
</Reference>
<Reference Include="Aki.Common">
<HintPath>References\Aki.Common.dll</HintPath>
</Reference>
<Reference Include="ComponentAce.Compression.Libs.zlib">
<HintPath>References\ComponentAce.Compression.Libs.zlib.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,185 @@
// NOTES:
// - redo search pattern;
// - compare both directories against eachother, not just one to the other
// - add ability to handle missing directories
using System.IO;
using Aki.Common.Utils;
using Aki.ByteBanger;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PatcherUtils
{
public class FileCompare
{
public string PatchBase;
public string TargetBase;
public string CompareBase;
private int fileCount;
private int fileIt;
private int diffCount = 0;
private int newCount = 0;
private int delCount = 0;
private int matchCount = 0;
private List<FileInfo> TargetPaths;
private List<FileInfo> ComparePaths;
private List<LineItem> AdditionalInfo = new List<LineItem>();
/// <summary>
/// Provides patch generation progress changes
/// </summary>
public event ProgressChangedHandler ProgressChanged;
protected virtual void RaiseProgressChanged(int progress, int total, string Message = "", params LineItem[] AdditionalLineItems)
{
int percent = (int)Math.Floor((double)progress / total * 100);
ProgressChanged?.Invoke(this, progress, total, percent, Message, AdditionalLineItems);
}
/// <summary>
/// Compare a target file to an assumed compareable file.
/// </summary>
/// <param name="targetFile">The known target path</param>
/// <param name="assumedCompareFile">The assumed comparable file path</param>
/// <returns>True if a comparison was made | False if a comparison could not be made</returns>
private bool Compare(string targetFile, string assumedCompareFile)
{
string patchFilePath = targetFile.Replace(TargetBase, PatchBase);
//we know our target file exists
byte[] targetData = VFS.ReadFile(targetFile);
if(!File.Exists(assumedCompareFile))
{
//save the data we won't have in our target as new
VFS.WriteFile($"{patchFilePath}.new", Zlib.Compress(targetData, ZlibCompression.Maximum));
newCount++;
return true;
}
//now our compare file is known to exist
byte[] compareData = VFS.ReadFile(assumedCompareFile);
// get diffs
DiffResult result = PatchUtil.Diff(compareData, targetData);
switch (result.Result)
{
case DiffResultType.Success:
VFS.WriteFile($"{patchFilePath}.bpf", result.PatchInfo.ToBytes());
diffCount++;
return true;
case DiffResultType.FilesMatch:
matchCount++;
return true;
default:
return false;
}
}
/// <summary>
/// Compares the base folders and generates patch files.
/// </summary>
/// <returns>True if patches were generated successfully | False if patch generation failed</returns>
public bool CompareAll()
{
DirectoryInfo targetDirInfo = new DirectoryInfo(TargetBase);
DirectoryInfo compareDirInfo = new DirectoryInfo(CompareBase);
AdditionalInfo.Add(new LineItem("Diff Patch", "0"));
AdditionalInfo.Add(new LineItem("New Patch", "0"));
AdditionalInfo.Add(new LineItem("Del Patch", "0"));
AdditionalInfo.Add(new LineItem("Files Match", "0"));
if (!targetDirInfo.Exists || !compareDirInfo.Exists)
{
Console.WriteLine("Target or Compare folder does not exist");
return false;
}
//Get all the files recursively
TargetPaths = new List<FileInfo>(targetDirInfo.GetFiles("*.*", SearchOption.AllDirectories));
ComparePaths = new List<FileInfo>(compareDirInfo.GetFiles("*.*", SearchOption.AllDirectories));
RaiseProgressChanged(0, fileCount, "Generating diffs...");
/* Comparing Target files -> Compare files
* - Exists = Diff (.bfd file)
* - Doesn't Exist = New (.new file)
*
* Once everything has been compared from one side, any remaining paths in our ComparePaths
* are things that don't exist in our target and can be deleted (.del file)
*/
for (int x = 0; x < TargetPaths.Count; x++)
{
FileInfo file = TargetPaths[x];
string assumedComparePath = file.DirectoryName.Replace(TargetBase, CompareBase);
if (!Compare(file.FullName, VFS.Combine(assumedComparePath, file.Name)))
{
return false;
}
//remove any existing files from our ComparePaths
FileInfo assumedFile = new FileInfo(VFS.Combine(assumedComparePath, file.Name));
if (assumedFile.Exists && ComparePaths.Exists(x => x.FullName == assumedFile.FullName))
{
ComparePaths.Remove(ComparePaths.Where(x => x.FullName == assumedFile.FullName).FirstOrDefault());
}
AdditionalInfo[0].ItemValue = diffCount.ToString();
AdditionalInfo[1].ItemValue = newCount.ToString();
AdditionalInfo[3].ItemValue = matchCount.ToString();
fileIt++;
RaiseProgressChanged(fileIt, fileCount, "", AdditionalInfo.ToArray());
}
if (ComparePaths.Count == 0)
{
//if there are no files to delete, just return true
return true;
}
//progress reset for files that need to be deleted
RaiseProgressChanged(0, ComparePaths.Count, "Processing .del files...");
fileIt = 0;
fileCount = ComparePaths.Count;
//the paths remaining in ComparePaths don't exist in our target and need to be removed during patching.
foreach (FileInfo file in ComparePaths)
{
//add del files replace root dir with patch base
string patchFilePath = file.FullName.Replace(CompareBase, PatchBase);
VFS.WriteFile($"{patchFilePath}.del", new byte[0]);
delCount++;
AdditionalInfo[2].ItemValue = delCount.ToString();
fileIt++;
RaiseProgressChanged(fileIt, fileCount, "", AdditionalInfo.ToArray());
}
return true;
}
public FileCompare(string TargetBase, string CompareBase, string PatchBase)
{
this.TargetBase = TargetBase;
this.CompareBase = CompareBase;
this.PatchBase = PatchBase;
fileCount = VFS.GetFilesCount(TargetBase);
fileIt = 0;
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.IO;
using Aki.Common.Utils;
using Aki.ByteBanger;
using System.Collections.Generic;
using System.Linq;
namespace PatcherUtils
{
public class FilePatcher
{
public string TargetBase;
public string PatchBase;
private int fileCount;
private int fileIt;
private int diffCount;
private int newCount;
private int delCount;
private List<LineItem> AdditionalInfo;
public event ProgressChangedHandler ProgressChanged;
protected virtual void RaiseProgressChanged(int progress, int total, string Message = "", params LineItem[] AdditionalLineItems)
{
int percent = (int)Math.Floor((double)progress / total * 100);
ProgressChanged?.Invoke(this, progress, total, percent, Message, AdditionalLineItems);
}
public bool Patch(string targetfile, string patchfile)
{
byte[] target = VFS.ReadFile(targetfile);
byte[] patch = VFS.ReadFile(patchfile);
PatchResult result = PatchUtil.Patch(target, PatchInfo.FromBytes(patch));
switch (result.Result)
{
case PatchResultType.Success:
VFS.WriteFile(targetfile, result.PatchedData);
return true;
case PatchResultType.AlreadyPatched:
case PatchResultType.InputChecksumMismatch:
case PatchResultType.InputLengthMismatch:
return true;
case PatchResultType.OutputChecksumMismatch:
default:
return false;
}
}
private bool PatchAll(string targetpath, string patchpath)
{
DirectoryInfo di = new DirectoryInfo(patchpath);
RaiseProgressChanged(0, fileCount, "Patching client...");
foreach (FileInfo file in di.GetFiles())
{
FileInfo target;
switch (file.Extension)
{
// patch
case ".bpf":
{
target = new FileInfo(VFS.Combine(targetpath, file.Name.Replace(".bpf", "")));
if (!Patch(target.FullName, file.FullName))
{
// patch failed
return false;
}
diffCount--;
}
break;
// add new files
case ".new":
{
target = new FileInfo(VFS.Combine(targetpath, file.Name.Replace(".new", "")));
VFS.WriteFile(target.FullName, Zlib.Decompress(VFS.ReadFile(file.FullName)));
newCount--;
}
break;
// delete old files
case ".del":
{
target = new FileInfo(VFS.Combine(targetpath, file.Name.Replace(".del", "")));
target.IsReadOnly = false;
target.Delete();
delCount--;
}
break;
}
AdditionalInfo[0].ItemValue = diffCount.ToString();
AdditionalInfo[1].ItemValue = newCount.ToString();
AdditionalInfo[2].ItemValue = delCount.ToString();
++fileIt;
RaiseProgressChanged(fileIt, fileCount, "", AdditionalInfo.ToArray());
}
foreach (DirectoryInfo directory in di.GetDirectories())
{
PatchAll(VFS.Combine(targetpath, directory.Name), directory.FullName);
}
di.Refresh();
if (di.GetFiles().Length == 0 && di.GetDirectories().Length == 0)
{
// remove empty folders
di.Delete();
}
return true;
}
public bool Run()
{
fileCount = VFS.GetFilesCount(PatchBase);
FileInfo[] files = new DirectoryInfo(PatchBase).GetFiles("*.*", SearchOption.AllDirectories);
diffCount = files.Where(x => x.Extension == ".bpf").Count();
newCount = files.Where(x => x.Extension == ".new").Count();
delCount = files.Where(x => x.Extension == ".del").Count();
AdditionalInfo = new List<LineItem>()
{
new LineItem("Patches Remaining", diffCount.ToString()),
new LineItem("New Files to Inflate", newCount.ToString()),
new LineItem("Files to Delete", delCount.ToString())
};
fileIt = 0;
return PatchAll(TargetBase, PatchBase);
}
}
}

View File

@ -0,0 +1,95 @@
using System.IO;
using System.Reflection;
namespace PatcherUtils
{
public class LazyOperations
{
/// <summary>
/// A directory to store temporary data.
/// </summary>
public static string TempDir = "PATCHER_TEMP".FromCwd();
private static string SevenZExe = "7za.exe";
/// <summary>
/// The path to the 7za.exe file in the <see cref="TempDir"/>
/// </summary>
public static string SevenZExePath = $"{TempDir}\\{SevenZExe}";
private static string PatcherClient = "patcher.exe";
/// <summary>
/// The path to the patcher.exe file in the <see cref="TempDir"/>
/// </summary>
public static string PatcherClientPath = $"{TempDir}\\{PatcherClient}";
/// <summary>
/// Streams embedded resources out of the assembly
/// </summary>
/// <param name="ResourceName"></param>
/// <param name="OutputFilePath"></param>
/// <remarks>The resource will not be streamed out if the <paramref name="OutputFilePath"/> already exists</remarks>
private static void StreamResourceOut(string ResourceName, string OutputFilePath)
{
FileInfo outputFile = new FileInfo(OutputFilePath);
if (outputFile.Exists) return;
if (!outputFile.Directory.Exists)
{
Directory.CreateDirectory(outputFile.Directory.FullName);
}
using (FileStream fs = File.Create(OutputFilePath))
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(ResourceName))
{
s.CopyTo(fs);
}
}
/// <summary>
/// Checks the resources in the assembly and streams them to the temp directory for later use.
/// </summary>
public static void PrepTempDir()
{
foreach(string resource in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
switch(resource)
{
case string a when a.EndsWith(SevenZExe):
{
StreamResourceOut(resource, SevenZExePath);
break;
}
case string a when a.EndsWith(PatcherClient):
{
StreamResourceOut(resource, PatcherClientPath);
break;
}
}
}
}
/// <summary>
/// Deletes the <see cref="TempDir"/> recursively
/// </summary>
/// <Returns>Returns true if the temp directory was deleted.</Returns>
public static bool CleanupTempDir()
{
DirectoryInfo dir = new DirectoryInfo(TempDir);
if(dir.Exists)
{
dir.Delete(true);
}
dir.Refresh();
if(dir.Exists)
{
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,15 @@
namespace PatcherUtils
{
public class LineItem
{
public string ItemText;
public string ItemValue;
public bool HasValue => ItemValue != "";
public LineItem(string ItemText, string ItemValue = "")
{
this.ItemText = ItemText;
this.ItemValue = ItemValue;
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Aki.ByteBanger">
<HintPath>..\Patcher\References\Aki.ByteBanger.dll</HintPath>
</Reference>
<Reference Include="Aki.Common">
<HintPath>..\Patcher\References\Aki.Common.dll</HintPath>
</Reference>
<Reference Include="ComponentAce.Compression.Libs.zlib">
<HintPath>..\Patcher\References\ComponentAce.Compression.Libs.zlib.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
namespace PatcherUtils
{
/// <summary>
/// <see cref="WriteProgressHandler(object, int, int, int, string, string[])"/> delegate
/// </summary>
/// <param name="Sender">The object calling the handler</param>
/// <param name="Progress">The current number of items processed</param>
/// <param name="Total">The total number of items</param>
/// <param name="Percent">The percentage of items processed</param>
/// <param name="Message">An optional message to display above the progress bar</param>
/// <param name="AdditionalLineItems">Additional information to display below the progress bar.</param>
public delegate void ProgressChangedHandler(object Sender, int Progress, int Total, int Percent, string Message = "", params LineItem[] AdditionalLineItems);
}

View File

@ -0,0 +1,13 @@
using System;
using System.IO;
namespace PatcherUtils
{
public static class RandomExtensions
{
public static string FromCwd(this string s)
{
return Path.Combine(Environment.CurrentDirectory, s);
}
}
}