diff --git a/Patcher/PatchClient/.gitignore b/Patcher/PatchClient/.gitignore
new file mode 100644
index 0000000..8afdcb6
--- /dev/null
+++ b/Patcher/PatchClient/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/Patcher/PatchClient/App.axaml b/Patcher/PatchClient/App.axaml
new file mode 100644
index 0000000..30a308b
--- /dev/null
+++ b/Patcher/PatchClient/App.axaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+ #121212
+ #FFC107
+ #FFFFFF
+ #282828
+ #323947
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/App.axaml.cs b/Patcher/PatchClient/App.axaml.cs
new file mode 100644
index 0000000..6b4a82a
--- /dev/null
+++ b/Patcher/PatchClient/App.axaml.cs
@@ -0,0 +1,29 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using PatchClient.ViewModels;
+using PatchClient.Views;
+
+namespace PatchClient
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = new MainWindowViewModel(),
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
diff --git a/Patcher/PatchClient/App.xaml b/Patcher/PatchClient/App.xaml
deleted file mode 100644
index 180f09e..0000000
--- a/Patcher/PatchClient/App.xaml
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
- #121212
- #FFC107
- #FFFFFF
- #282828
- #323947
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Patcher/PatchClient/App.xaml.cs b/Patcher/PatchClient/App.xaml.cs
deleted file mode 100644
index 2a36935..0000000
--- a/Patcher/PatchClient/App.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace PatchClient
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- }
-}
diff --git a/Patcher/PatchClient/AssemblyInfo.cs b/Patcher/PatchClient/AssemblyInfo.cs
deleted file mode 100644
index 8b5504e..0000000
--- a/Patcher/PatchClient/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-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)
-)]
diff --git a/Patcher/PatchClient/Assets/Styles.axaml b/Patcher/PatchClient/Assets/Styles.axaml
new file mode 100644
index 0000000..0fbcd7f
--- /dev/null
+++ b/Patcher/PatchClient/Assets/Styles.axaml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/Assets/avalonia-logo.ico b/Patcher/PatchClient/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..da8d49f
Binary files /dev/null and b/Patcher/PatchClient/Assets/avalonia-logo.ico differ
diff --git a/Patcher/PatchClient/CustomControls/TitleBar.axaml b/Patcher/PatchClient/CustomControls/TitleBar.axaml
new file mode 100644
index 0000000..eb9d4eb
--- /dev/null
+++ b/Patcher/PatchClient/CustomControls/TitleBar.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/CustomControls/TitleBar.axaml.cs b/Patcher/PatchClient/CustomControls/TitleBar.axaml.cs
new file mode 100644
index 0000000..75f094a
--- /dev/null
+++ b/Patcher/PatchClient/CustomControls/TitleBar.axaml.cs
@@ -0,0 +1,67 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System.Windows.Input;
+
+namespace PatchClient.CustomControls
+{
+ public partial class TitleBar : UserControl
+ {
+ public TitleBar()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public static readonly StyledProperty TitleProperty =
+ AvaloniaProperty.Register(nameof(Title));
+
+ public string Title
+ {
+ get => GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ public static readonly StyledProperty XButtonForegroundProperty =
+ AvaloniaProperty.Register(nameof(XButtonForeground));
+
+ public IBrush XButtonForeground
+ {
+ get => GetValue(XButtonForegroundProperty);
+ set => SetValue(XButtonForegroundProperty, value);
+ }
+
+ public static new readonly StyledProperty ForegroundProperty =
+ AvaloniaProperty.Register(nameof(Foreground));
+
+ public new IBrush Foreground
+ {
+ get => GetValue(ForegroundProperty);
+ set => SetValue(ForegroundProperty, value);
+ }
+
+ public static new readonly StyledProperty BackgroundProperty =
+ AvaloniaProperty.Register(nameof(Background));
+
+ public new IBrush Background
+ {
+ get => GetValue(BackgroundProperty);
+ set => SetValue(BackgroundProperty, value);
+ }
+
+ //Close Button Command (X Button) Property
+ public static readonly StyledProperty XButtonCommandProperty =
+ AvaloniaProperty.Register(nameof(XButtonCommand));
+
+ public ICommand XButtonCommand
+ {
+ get => GetValue(XButtonCommandProperty);
+ set => SetValue(XButtonCommandProperty, value);
+ }
+ }
+}
diff --git a/Patcher/PatchClient/Extensions/ControlExtensions.cs b/Patcher/PatchClient/Extensions/ControlExtensions.cs
deleted file mode 100644
index a635dde..0000000
--- a/Patcher/PatchClient/Extensions/ControlExtensions.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Shell;
-
-namespace PatchClient.Extensions
-{
- public static class ControlExtensions
- {
- public static void DispatcherSetValue(this ProgressBar pb, int Value)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- pb.Value = Value;
- });
- }
-
- public static void DispatcherSetIndetermination(this ProgressBar pb, bool Indeterminate)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- pb.IsIndeterminate = Indeterminate;
- });
- }
-
- public static void DispaatcherSetContent(this ContentControl cc, object content)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- cc.Content = content;
- });
- }
-
- public static void DispatcherSetText(this TextBlock tb, string Text)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- tb.Text = Text;
- });
- }
-
- public static void DispatcherSetEnabled(this UIElement uie, bool Enabled)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- uie.IsEnabled = Enabled;
- });
- }
- }
-}
diff --git a/Patcher/PatchClient/MainWindow.xaml b/Patcher/PatchClient/MainWindow.xaml
deleted file mode 100644
index 5cb92b5..0000000
--- a/Patcher/PatchClient/MainWindow.xaml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Patcher/PatchClient/MainWindow.xaml.cs b/Patcher/PatchClient/MainWindow.xaml.cs
deleted file mode 100644
index 5f222aa..0000000
--- a/Patcher/PatchClient/MainWindow.xaml.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using PatchClient.Extensions;
-using PatcherUtils;
-using System;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Input;
-
-namespace PatchClient
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- }
-
- private void RunPatcher()
- {
- Task.Run(() =>
- {
- PatchHelper patcher = new PatchHelper(Environment.CurrentDirectory, null, LazyOperations.PatchFolder.FromCwd());
-
- patcher.ProgressChanged += patcher_ProgressChanged;
-
- try
- {
- LazyOperations.CleanupTempDir();
- LazyOperations.PrepTempDir();
-
- string message = patcher.ApplyPatches();
-
- MessageBox.Show(message, "Patcher");
- }
- catch(Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- finally
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- Application.Current.Shutdown(0);
- });
- }
- });
- }
-
- private void patcher_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";
- }
-
-
- PatchProgressBar.DispatcherSetValue(Percent);
-
- if (!string.IsNullOrWhiteSpace(Message))
- {
- PatchMessageLabel.DispaatcherSetContent(Message);
- }
-
- PatchProgressInfoLabel.DispaatcherSetContent($"[{Progress}/{Total}]");
-
- AdditionalInfoBlock.DispatcherSetText(additionalInfo);
- }
-
- private void Window_Loaded(object sender, RoutedEventArgs e)
- {
- RunPatcher();
- }
-
- private void Close_Button_Click(object sender, RoutedEventArgs e)
- {
- Application.Current.Shutdown(0);
- }
-
- private void label_topbar_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (e.ChangedButton == MouseButton.Left)
- {
- this.DragMove();
- }
- }
- }
-}
diff --git a/Patcher/PatchClient/Models/LineItemProgress.cs b/Patcher/PatchClient/Models/LineItemProgress.cs
new file mode 100644
index 0000000..fef4514
--- /dev/null
+++ b/Patcher/PatchClient/Models/LineItemProgress.cs
@@ -0,0 +1,61 @@
+using PatcherUtils;
+using ReactiveUI;
+using System;
+
+namespace PatchClient.Models
+{
+ public class LineItemProgress : ReactiveObject
+ {
+ private bool _Completed = false;
+ public bool Completed
+ {
+ get => _Completed;
+ set => this.RaiseAndSetIfChanged(ref _Completed, value);
+ }
+
+ public int Total { get; private set; } = 0;
+
+ private string _Info = "";
+ public string Info
+ {
+ get => _Info;
+ set => this.RaiseAndSetIfChanged(ref _Info, value);
+ }
+
+ private int _Progress;
+ public int Progress
+ {
+ get => _Progress;
+ set => this.RaiseAndSetIfChanged(ref _Progress, value);
+ }
+
+ private string _ProgressInfo = "";
+ public string ProgressInfo
+ {
+ get => _ProgressInfo;
+ set => this.RaiseAndSetIfChanged(ref _ProgressInfo, value);
+ }
+
+ public void UpdateProgress(int RemainingCount)
+ {
+ if (Completed) return;
+
+ int processed = Total - RemainingCount;
+
+ Progress = (int)Math.Floor((double)processed / Total * 100);
+
+ ProgressInfo = $"{processed} / {Total}";
+
+ if (Progress == 100) Completed = true;
+ }
+
+ public LineItemProgress(LineItem Item)
+ {
+ Info = Item.ItemText;
+
+ Total = Item.ItemValue;
+
+ Progress = (int)Math.Floor((double)Item.ItemValue / Total * 100);
+ }
+ }
+}
diff --git a/Patcher/PatchClient/Models/ViewNavigator.cs b/Patcher/PatchClient/Models/ViewNavigator.cs
new file mode 100644
index 0000000..c407718
--- /dev/null
+++ b/Patcher/PatchClient/Models/ViewNavigator.cs
@@ -0,0 +1,14 @@
+using ReactiveUI;
+
+namespace PatchClient.Models
+{
+ public class ViewNavigator : ReactiveObject
+ {
+ private object _SelectedViewModel;
+ public object SelectedViewModel
+ {
+ get => _SelectedViewModel;
+ set => this.RaiseAndSetIfChanged(ref _SelectedViewModel, value);
+ }
+ }
+}
diff --git a/Patcher/PatchClient/PatchClient.csproj b/Patcher/PatchClient/PatchClient.csproj
index db0004b..55eb282 100644
--- a/Patcher/PatchClient/PatchClient.csproj
+++ b/Patcher/PatchClient/PatchClient.csproj
@@ -1,15 +1,16 @@
-
WinExe
- net5.0-windows
- true
+ net6.0
+ true
+ enable
-
+
+
+
-
@@ -18,4 +19,17 @@
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
diff --git a/Patcher/PatchClient/Program.cs b/Patcher/PatchClient/Program.cs
new file mode 100644
index 0000000..bc1e0c4
--- /dev/null
+++ b/Patcher/PatchClient/Program.cs
@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+using System;
+
+namespace PatchClient
+{
+ class Program
+ {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace()
+ .UseReactiveUI();
+ }
+}
diff --git a/Patcher/PatchClient/Properties/launchSettings.json b/Patcher/PatchClient/Properties/launchSettings.json
index e0551b4..234c9c0 100644
--- a/Patcher/PatchClient/Properties/launchSettings.json
+++ b/Patcher/PatchClient/Properties/launchSettings.json
@@ -1,7 +1,8 @@
{
"profiles": {
"PatchClient": {
- "commandName": "Project"
+ "commandName": "Project",
+ "hotReloadEnabled": false
}
}
}
\ No newline at end of file
diff --git a/Patcher/PatchClient/ViewLocator.cs b/Patcher/PatchClient/ViewLocator.cs
new file mode 100644
index 0000000..36657b0
--- /dev/null
+++ b/Patcher/PatchClient/ViewLocator.cs
@@ -0,0 +1,30 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using PatchClient.ViewModels;
+using System;
+
+namespace PatchClient
+{
+ public class ViewLocator : IDataTemplate
+ {
+ public IControl Build(object data)
+ {
+ var name = data.GetType().FullName!.Replace("ViewModel", "View");
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+ else
+ {
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+ }
+
+ public bool Match(object data)
+ {
+ return data is ViewModelBase;
+ }
+ }
+}
diff --git a/Patcher/PatchClient/ViewModels/MainWindowViewModel.cs b/Patcher/PatchClient/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..f56e6b7
--- /dev/null
+++ b/Patcher/PatchClient/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,27 @@
+using Avalonia;
+using PatchClient.Models;
+using ReactiveUI;
+using Splat;
+using System.Windows.Input;
+
+namespace PatchClient.ViewModels
+{
+ public class MainWindowViewModel : ViewModelBase
+ {
+ public ICommand CloseCommand => ReactiveCommand.Create(() =>
+ {
+ if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
+ {
+ desktopApp.MainWindow.Close();
+ }
+ });
+
+ public ViewNavigator navigator { get; set; } = new ViewNavigator();
+ public MainWindowViewModel()
+ {
+ navigator.SelectedViewModel = new PatcherViewModel();
+
+ Locator.CurrentMutable.RegisterConstant(navigator, typeof(ViewNavigator));
+ }
+ }
+}
diff --git a/Patcher/PatchClient/ViewModels/MessageViewModel.cs b/Patcher/PatchClient/ViewModels/MessageViewModel.cs
new file mode 100644
index 0000000..4eecaf2
--- /dev/null
+++ b/Patcher/PatchClient/ViewModels/MessageViewModel.cs
@@ -0,0 +1,19 @@
+using ReactiveUI;
+
+namespace PatchClient.ViewModels
+{
+ public class MessageViewModel : ViewModelBase
+ {
+ private string _InfoText;
+ public string InfoText
+ {
+ get => _InfoText;
+ set => this.RaiseAndSetIfChanged(ref _InfoText, value);
+ }
+
+ public MessageViewModel(string Message)
+ {
+ InfoText = Message;
+ }
+ }
+}
diff --git a/Patcher/PatchClient/ViewModels/PatcherViewModel.cs b/Patcher/PatchClient/ViewModels/PatcherViewModel.cs
new file mode 100644
index 0000000..6eaa5da
--- /dev/null
+++ b/Patcher/PatchClient/ViewModels/PatcherViewModel.cs
@@ -0,0 +1,92 @@
+using Avalonia;
+using PatchClient.Models;
+using PatcherUtils;
+using ReactiveUI;
+using Splat;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace PatchClient.ViewModels
+{
+ public class PatcherViewModel : ViewModelBase
+ {
+ private bool initLineItemProgress = true;
+
+ public ObservableCollection LineItems { get; set; } = new ObservableCollection();
+
+ private string _ProgressMessage;
+ public string ProgressMessage
+ {
+ get => _ProgressMessage;
+ set => this.RaiseAndSetIfChanged(ref _ProgressMessage, value);
+ }
+
+ private int _PatchPercent;
+ public int PatchPercent
+ {
+ get => _PatchPercent;
+ set => this.RaiseAndSetIfChanged(ref _PatchPercent, value);
+ }
+
+ private string _PatchMessage;
+ public string PatchMessage
+ {
+ get => _PatchMessage;
+ set => this.RaiseAndSetIfChanged(ref _PatchMessage, value);
+ }
+
+ private ViewNavigator navigator => Locator.Current.GetService();
+
+ public PatcherViewModel()
+ {
+ RunPatcher();
+ }
+
+ private void RunPatcher()
+ {
+ Task.Run(() =>
+ {
+ //Slight delay to avoid some weird race condition in avalonia core, seems to be a bug, but also maybe I'm just stupid, idk -waffle
+ //Error without delay: An item with the same key has already been added. Key: [1, Avalonia.Controls.Generators.ItemContainerInfo]
+ System.Threading.Thread.Sleep(1000);
+
+ PatchHelper patcher = new PatchHelper(Environment.CurrentDirectory, null, LazyOperations.PatchFolder);
+
+ patcher.ProgressChanged += patcher_ProgressChanged;
+
+ string message = patcher.ApplyPatches();
+
+ navigator.SelectedViewModel = new MessageViewModel(message).WithDelay(400);
+ });
+ }
+
+ private void patcher_ProgressChanged(object Sender, int Progress, int Total, int Percent, string Message = "", params LineItem[] AdditionalLineItems)
+ {
+ foreach (LineItem item in AdditionalLineItems)
+ {
+
+ if (initLineItemProgress)
+ {
+ if (item.ItemValue <= 0) continue;
+
+ LineItems.Add(new LineItemProgress(item));
+ }
+
+ LineItems.FirstOrDefault(x => x.Info == item.ItemText).UpdateProgress(item.ItemValue);
+ }
+
+ initLineItemProgress = false;
+
+ PatchPercent = Percent;
+
+ if (!string.IsNullOrWhiteSpace(Message))
+ {
+ PatchMessage = Message;
+ }
+
+ ProgressMessage = $"Patching: {Progress} / {Total} - {Percent}%";
+ }
+ }
+}
diff --git a/Patcher/PatchClient/ViewModels/ViewModelBase.cs b/Patcher/PatchClient/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..ae56d9e
--- /dev/null
+++ b/Patcher/PatchClient/ViewModels/ViewModelBase.cs
@@ -0,0 +1,22 @@
+using PatchClient.Models;
+using ReactiveUI;
+
+namespace PatchClient.ViewModels
+{
+ public class ViewModelBase : ReactiveObject
+ {
+ ///
+ /// Delay the return of the viewmodel
+ ///
+ /// The amount of time in milliseconds to delay
+ /// The viewmodel after the delay time
+ /// Useful to delay the navigation to another view via the . For instance, to allow an animation to complete.
+ public ViewModelBase WithDelay(int Milliseconds)
+ {
+ System.Threading.Thread.Sleep(Milliseconds);
+
+ return this;
+ }
+ }
+
+}
diff --git a/Patcher/PatchClient/Views/MainWindow.axaml b/Patcher/PatchClient/Views/MainWindow.axaml
new file mode 100644
index 0000000..2c3cde2
--- /dev/null
+++ b/Patcher/PatchClient/Views/MainWindow.axaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/Views/MainWindow.axaml.cs b/Patcher/PatchClient/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..78a19d3
--- /dev/null
+++ b/Patcher/PatchClient/Views/MainWindow.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PatchClient.Views
+{
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/Patcher/PatchClient/Views/MessageView.axaml b/Patcher/PatchClient/Views/MessageView.axaml
new file mode 100644
index 0000000..9d52fd9
--- /dev/null
+++ b/Patcher/PatchClient/Views/MessageView.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/Views/MessageView.axaml.cs b/Patcher/PatchClient/Views/MessageView.axaml.cs
new file mode 100644
index 0000000..dab0189
--- /dev/null
+++ b/Patcher/PatchClient/Views/MessageView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PatchClient.Views
+{
+ public partial class MessageView : UserControl
+ {
+ public MessageView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/Patcher/PatchClient/Views/PatcherView.axaml b/Patcher/PatchClient/Views/PatcherView.axaml
new file mode 100644
index 0000000..1197554
--- /dev/null
+++ b/Patcher/PatchClient/Views/PatcherView.axaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchClient/Views/PatcherView.axaml.cs b/Patcher/PatchClient/Views/PatcherView.axaml.cs
new file mode 100644
index 0000000..d8ad362
--- /dev/null
+++ b/Patcher/PatchClient/Views/PatcherView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PatchClient.Views
+{
+ public partial class PatcherView : UserControl
+ {
+ public PatcherView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/.gitignore b/Patcher/PatchGenerator/.gitignore
new file mode 100644
index 0000000..8afdcb6
--- /dev/null
+++ b/Patcher/PatchGenerator/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/Patcher/PatchGenerator/App.axaml b/Patcher/PatchGenerator/App.axaml
new file mode 100644
index 0000000..7cab700
--- /dev/null
+++ b/Patcher/PatchGenerator/App.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ #121212
+ #FFC107
+ #FFFFFF
+ #282828
+ #323947
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/App.axaml.cs b/Patcher/PatchGenerator/App.axaml.cs
new file mode 100644
index 0000000..88b1ceb
--- /dev/null
+++ b/Patcher/PatchGenerator/App.axaml.cs
@@ -0,0 +1,40 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using PatchGenerator.Models;
+using PatchGenerator.ViewModels;
+using PatchGenerator.Views;
+
+namespace PatchGenerator
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.Startup += Desktop_Startup;
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ private void Desktop_Startup(object? sender, ControlledApplicationLifetimeStartupEventArgs e)
+ {
+ if (sender is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ GenStartupArgs genArgs = GenStartupArgs.Parse(e.Args);
+
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = new MainWindowViewModel(genArgs)
+ };
+ }
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/App.xaml b/Patcher/PatchGenerator/App.xaml
deleted file mode 100644
index 3341652..0000000
--- a/Patcher/PatchGenerator/App.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/Patcher/PatchGenerator/App.xaml.cs b/Patcher/PatchGenerator/App.xaml.cs
deleted file mode 100644
index b842762..0000000
--- a/Patcher/PatchGenerator/App.xaml.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Windows;
-
-namespace PatchGenerator
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- private void Application_Startup(object sender, StartupEventArgs e)
- {
- GenStartupArgs startupArgs = null;
-
- if (e.Args != null && e.Args.Length > 0)
- {
- if(e.Args[0].ToLower() == "help")
- {
- System.Text.StringBuilder sb = new System.Text.StringBuilder()
- .AppendLine("Help - Shows this message box if in position 1")
- .AppendLine("")
- .AppendLine("Parameters below can be used like this: \"Name::Value\"")
- .AppendLine("OutputFolderName - The output file for the patch")
- .AppendLine("TargetFolderPath - The target folder path")
- .AppendLine("CompareFolderPath - The compare folder path")
- .AppendLine("AutoZip - Set if the output folder should be zipped up after patch generation. Defaults to true");
-
- MessageBox.Show(sb.ToString(), "Parameter Help Info", MessageBoxButton.OK, MessageBoxImage.Information);
-
- Application.Current.Shutdown(0);
- }
-
- startupArgs = GenStartupArgs.Parse(e.Args);
- }
-
- MainWindow mw = new MainWindow(startupArgs);
-
- mw.ShowDialog();
- }
- }
-}
diff --git a/Patcher/PatchGenerator/AssemblyInfo.cs b/Patcher/PatchGenerator/AssemblyInfo.cs
deleted file mode 100644
index 8b5504e..0000000
--- a/Patcher/PatchGenerator/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-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)
-)]
diff --git a/Patcher/PatchGenerator/Assets/Styles.axaml b/Patcher/PatchGenerator/Assets/Styles.axaml
new file mode 100644
index 0000000..95c8ee6
--- /dev/null
+++ b/Patcher/PatchGenerator/Assets/Styles.axaml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/Assets/avalonia-logo.ico b/Patcher/PatchGenerator/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..da8d49f
Binary files /dev/null and b/Patcher/PatchGenerator/Assets/avalonia-logo.ico differ
diff --git a/Patcher/PatchGenerator/AttachedProperties/RandomBoolAttProp.cs b/Patcher/PatchGenerator/AttachedProperties/RandomBoolAttProp.cs
new file mode 100644
index 0000000..5b2eb0d
--- /dev/null
+++ b/Patcher/PatchGenerator/AttachedProperties/RandomBoolAttProp.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls;
+
+namespace PatchGenerator.AttachedProperties
+{
+ ///
+ /// Just a random boolean value for you to attach and use to any control.
+ ///
+ public class RandomBoolAttProp : AvaloniaObject
+ {
+ public static readonly AttachedProperty RandomBoolProperty =
+ AvaloniaProperty.RegisterAttached("RandomBool");
+
+ public static bool GetRandomBool(Control control)
+ {
+ return control.GetValue(RandomBoolProperty);
+ }
+
+ public static void SetRandomBool(Control control, bool value)
+ {
+ control.SetValue(RandomBoolProperty, value);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml b/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml
new file mode 100644
index 0000000..5b5a529
--- /dev/null
+++ b/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml.cs b/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml.cs
new file mode 100644
index 0000000..780cf97
--- /dev/null
+++ b/Patcher/PatchGenerator/CustomControls/FolderSelector.axaml.cs
@@ -0,0 +1,69 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using System.IO;
+using System.Linq;
+
+namespace PatchGenerator.CustomControls
+{
+ public partial class FolderSelector : UserControl
+ {
+ public FolderSelector()
+ {
+ InitializeComponent();
+
+ AddHandler(DragDrop.DropEvent, Drop);
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void Drop(object sender, DragEventArgs e)
+ {
+ if (e.Data.Contains(DataFormats.FileNames))
+ {
+ string[] filePaths = e.Data.GetFileNames().ToArray();
+
+ if (filePaths.Length == 1)
+ {
+ DirectoryInfo folder = new DirectoryInfo(filePaths[0]);
+
+ if (folder.Exists)
+ {
+ FolderPath = filePaths[0];
+ FolderSelected = true;
+ return;
+ }
+
+ FolderPath = "Dropped object must be a folder";
+ FolderSelected = false;
+ return;
+ }
+
+ FolderPath = "Cannot drop multiple files";
+ FolderSelected = false;
+ }
+ }
+
+ private static readonly StyledProperty FolderSelectedProperty =
+ AvaloniaProperty.Register(nameof(FolderSelected));
+
+ private bool FolderSelected
+ {
+ get => GetValue(FolderSelectedProperty);
+ set => SetValue(FolderSelectedProperty, value);
+ }
+
+ public static readonly StyledProperty FolderPathProperty =
+ AvaloniaProperty.Register(nameof(FolderPath));
+
+ public string FolderPath
+ {
+ get => GetValue(FolderPathProperty);
+ set => SetValue(FolderPathProperty, value);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/CustomControls/TitleBar.axaml b/Patcher/PatchGenerator/CustomControls/TitleBar.axaml
new file mode 100644
index 0000000..952bdae
--- /dev/null
+++ b/Patcher/PatchGenerator/CustomControls/TitleBar.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/CustomControls/TitleBar.axaml.cs b/Patcher/PatchGenerator/CustomControls/TitleBar.axaml.cs
new file mode 100644
index 0000000..b8a3df0
--- /dev/null
+++ b/Patcher/PatchGenerator/CustomControls/TitleBar.axaml.cs
@@ -0,0 +1,67 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System.Windows.Input;
+
+namespace PatchGenerator.CustomControls
+{
+ public partial class TitleBar : UserControl
+ {
+ public TitleBar()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public static readonly StyledProperty TitleProperty =
+ AvaloniaProperty.Register(nameof(Title));
+
+ public string Title
+ {
+ get => GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ public static readonly StyledProperty XButtonForegroundProperty =
+ AvaloniaProperty.Register(nameof(XButtonForeground));
+
+ public IBrush XButtonForeground
+ {
+ get => GetValue(XButtonForegroundProperty);
+ set => SetValue(XButtonForegroundProperty, value);
+ }
+
+ public static new readonly StyledProperty ForegroundProperty =
+ AvaloniaProperty.Register(nameof(Foreground));
+
+ public new IBrush Foreground
+ {
+ get => GetValue(ForegroundProperty);
+ set => SetValue(ForegroundProperty, value);
+ }
+
+ public static new readonly StyledProperty BackgroundProperty =
+ AvaloniaProperty.Register(nameof(Background));
+
+ public new IBrush Background
+ {
+ get => GetValue(BackgroundProperty);
+ set => SetValue(BackgroundProperty, value);
+ }
+
+ //Close Button Command (X Button) Property
+ public static readonly StyledProperty XButtonCommandProperty =
+ AvaloniaProperty.Register(nameof(XButtonCommand));
+
+ public ICommand XButtonCommand
+ {
+ get => GetValue(XButtonCommandProperty);
+ set => SetValue(XButtonCommandProperty, value);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/Extensions/ControlExtensions.cs b/Patcher/PatchGenerator/Extensions/ControlExtensions.cs
deleted file mode 100644
index e91824b..0000000
--- a/Patcher/PatchGenerator/Extensions/ControlExtensions.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Windows;
-using System.Windows.Controls;
-
-namespace PatchGenerator.Extensions
-{
- public static class ControlExtensions
- {
- public static void DispatcherSetValue(this ProgressBar pb, int Value)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- pb.Value = Value;
- });
- }
-
- public static void DispatcherSetIndetermination(this ProgressBar pb, bool Indeterminate)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- pb.IsIndeterminate = Indeterminate;
- });
- }
-
- public static void DispaatcherSetContent(this ContentControl cc, object content)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- cc.Content = content;
- });
- }
-
- public static void DispatcherSetText(this TextBlock tb, string Text)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- tb.Text = Text;
- });
- }
-
- public static void DispatcherSetEnabled(this UIElement uie, bool Enabled)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- uie.IsEnabled = Enabled;
- });
- }
- }
-}
diff --git a/Patcher/PatchGenerator/Helpers/PatchItemDefinitions.cs b/Patcher/PatchGenerator/Helpers/PatchItemDefinitions.cs
new file mode 100644
index 0000000..0c5e800
--- /dev/null
+++ b/Patcher/PatchGenerator/Helpers/PatchItemDefinitions.cs
@@ -0,0 +1,16 @@
+using Avalonia.Media;
+using System.Collections.Generic;
+
+namespace PatchGenerator.Helpers
+{
+ public static class PatchItemDefinitions
+ {
+ public static Dictionary Colors = new Dictionary()
+ {
+ {"delta", Brushes.MediumPurple },
+ {"new", Brushes.Green },
+ {"del", Brushes.IndianRed },
+ {"exists", Brushes.DimGray }
+ };
+ }
+}
diff --git a/Patcher/PatchGenerator/MainWindow.xaml b/Patcher/PatchGenerator/MainWindow.xaml
deleted file mode 100644
index 1da4dcf..0000000
--- a/Patcher/PatchGenerator/MainWindow.xaml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Patcher/PatchGenerator/MainWindow.xaml.cs b/Patcher/PatchGenerator/MainWindow.xaml.cs
deleted file mode 100644
index 90ffea5..0000000
--- a/Patcher/PatchGenerator/MainWindow.xaml.cs
+++ /dev/null
@@ -1,253 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Media;
-using PatcherUtils;
-using PatchGenerator.Extensions;
-
-namespace PatchGenerator
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- private string compareFolder = "";
- private string targetFolder = "";
- private string outputFolderName = "";
- public bool AutoZip { get; set; } = true;
-
- private Stopwatch stopwatch = new Stopwatch();
-
- public MainWindow()
- {
- InitializeComponent();
- }
-
- public MainWindow(GenStartupArgs startupArgs = null)
- {
- InitializeComponent();
-
- if (startupArgs == null) return;
-
- FileNameBox.Text = startupArgs.OutputFolderName;
- if (startupArgs.CompareFolderPath != "")
- {
- compareFolder = startupArgs.CompareFolderPath;
- CompareLabel.Content = $"Compare Folder:\n{compareFolder}";
- CompareLabel.BorderBrush = Brushes.DarkCyan;
- }
-
- if (startupArgs.TargetFolderPath != "")
- {
- targetFolder = startupArgs.TargetFolderPath;
- TargetLabel.Content = $"Target Folder:\n{targetFolder}";
- TargetLabel.BorderBrush = Brushes.DarkCyan;
- }
-
- AutoZip = startupArgs.AutoZip;
-
- if (startupArgs.ReadyToRun)
- {
- GenButton_Click(null, null);
- }
- }
-
- 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(string patchBase)
- {
- //create temp data
- GenProgressBar.DispatcherSetIndetermination(true);
- GenProgressMessageLabel.DispaatcherSetContent("Extracting temp data ...");
-
- LazyOperations.CleanupTempDir();
- LazyOperations.PrepTempDir();
-
- GenProgressBar.DispatcherSetIndetermination(false);
-
- //generate patches
- //TODO - fix these weird variable names (why did I do this?)
- PatchHelper patcher = new PatchHelper(compareFolder, targetFolder, patchBase);
-
- patcher.ProgressChanged += patcher_ProgressChanged;
-
- if (!patcher.GeneratePatches())
- {
- MessageBox.Show("One of the provided folder paths doesn't exist.", "Oops :(", MessageBoxButton.OK, MessageBoxImage.Error);
- }
-
- //Copy patch client to output folder
- File.Copy(LazyOperations.PatcherClientPath, $"{outputFolderName}\\patcher.exe", true);
-
- //compress patch output folder to 7z file
- if (AutoZip)
- {
- LazyOperations.StartZipProcess(outputFolderName, $"{outputFolderName}.7z".FromCwd());
- }
-
- GenProgressBar.DispatcherSetValue(100);
- GenProgressMessageLabel.DispaatcherSetContent("Done");
- }
-
- private void patcher_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";
- }
-
-
- GenProgressBar.DispatcherSetValue(Percent);
-
- if (!string.IsNullOrWhiteSpace(Message))
- {
- GenProgressMessageLabel.DispaatcherSetContent(Message);
- }
-
- GenProgressInfoLabel.DispaatcherSetContent($"[{Progress}/{Total}]");
-
- AdditionalInfoBlock.DispatcherSetText(additionalInfo);
- }
-
- private void GenButton_Click(object sender, RoutedEventArgs e)
- {
- GenButton.IsEnabled = false;
- CompareLabel.IsEnabled = false;
- TargetLabel.IsEnabled = false;
- FileNameBox.IsEnabled = false;
- AutoZip_checkBox.IsEnabled = false;
-
- string InfoNeededMessage = "You must set the following: ";
- bool infoNeeded = false;
-
- if(string.IsNullOrWhiteSpace(FileNameBox.Text))
- {
- InfoNeededMessage += "\n[Output File Name]";
- FileNameBox.BorderBrush = Brushes.Red;
- infoNeeded = true;
- }
-
- if(string.IsNullOrWhiteSpace(compareFolder))
- {
- InfoNeededMessage += "\n[COMPARE Folder]";
- CompareLabel.BorderBrush = Brushes.Red;
- infoNeeded = true;
- }
-
- if(string.IsNullOrWhiteSpace(targetFolder))
- {
- InfoNeededMessage += "\n[TARGET Folder]";
- TargetLabel.BorderBrush = Brushes.Red;
- infoNeeded = true;
- }
-
- if (infoNeeded)
- {
- MessageBox.Show(InfoNeededMessage, "Info Required", MessageBoxButton.OK, MessageBoxImage.Warning);
- GenButton.IsEnabled = true;
- CompareLabel.IsEnabled = true;
- TargetLabel.IsEnabled = true;
- FileNameBox.IsEnabled = true;
- AutoZip_checkBox.IsEnabled = true;
- return;
- }
-
- void SetEndingInfo(string info)
- {
- GenButton.DispatcherSetEnabled(true);
- CompareLabel.DispatcherSetEnabled(true);
- TargetLabel.DispatcherSetEnabled(true);
- FileNameBox.DispatcherSetEnabled(true);
- AutoZip_checkBox.DispatcherSetEnabled(true);
-
- GenProgressMessageLabel.DispaatcherSetContent("");
- GenProgressInfoLabel.DispaatcherSetContent(info);
- }
-
-
- Task.Run(() =>
- {
- stopwatch.Reset();
- stopwatch.Start();
-
- try
- {
- GeneratePatches(Path.Combine(outputFolderName.FromCwd(), LazyOperations.PatchFolder));
- stopwatch.Stop();
- SetEndingInfo($"Patches Generated in: {GetStopWatchTime()}");
- }
- catch(Exception ex)
- {
- stopwatch.Stop();
- SetEndingInfo(ex.Message);
- }
- });
- }
-
- private void FileNameBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
- {
- FileNameBox.BorderBrush = Brushes.Gainsboro;
-
- if (outputFolderName == FileNameBox.Text) return;
-
- outputFolderName = Regex.Replace(FileNameBox.Text, "[^A-Za-z0-9.\\-_]", "");
-
- FileNameBox.Text = outputFolderName;
- }
- }
-}
diff --git a/Patcher/PatchGenerator/GenStartupArgs.cs b/Patcher/PatchGenerator/Models/GenStartupArgs.cs
similarity index 57%
rename from Patcher/PatchGenerator/GenStartupArgs.cs
rename to Patcher/PatchGenerator/Models/GenStartupArgs.cs
index f126dee..4b80a84 100644
--- a/Patcher/PatchGenerator/GenStartupArgs.cs
+++ b/Patcher/PatchGenerator/Models/GenStartupArgs.cs
@@ -1,22 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PatchGenerator
+namespace PatchGenerator.Models
{
public class GenStartupArgs
{
- public bool ReadyToRun => OutputFolderName != "" && CompareFolderPath != "" && TargetFolderPath != "";
+ public bool ReadyToRun => OutputFolderName != "" && SourceFolderPath != "" && TargetFolderPath != "";
public string OutputFolderName { get; private set; } = "";
- public string CompareFolderPath { get; private set; } = "";
+ public string SourceFolderPath { get; private set; } = "";
public string TargetFolderPath { get; private set; } = "";
public bool AutoZip { get; private set; } = true;
- protected GenStartupArgs(string OutputFolderName, string CompareFolderPath, string TargetFolderPath, bool AutoZip)
+ protected GenStartupArgs(string OutputFolderName, string SourceFolderPath, string TargetFolderPath, bool AutoZip)
{
this.OutputFolderName = OutputFolderName;
- this.CompareFolderPath = CompareFolderPath;
+ this.SourceFolderPath = SourceFolderPath;
this.TargetFolderPath = TargetFolderPath;
this.AutoZip = AutoZip;
}
@@ -25,43 +19,43 @@ namespace PatchGenerator
{
if (Args == null || Args.Length == 0) return null;
- string ofn = "";
- string cfp = "";
- string tfp = "";
- bool az = true;
+ string outputFolderPath = "";
+ string sourceFolderPath = "";
+ string targetFolderPath = "";
+ bool autoZip = true;
- foreach(string arg in Args)
+ foreach (string arg in Args)
{
if (arg.Split("::").Length != 2) return null;
var argSplit = arg.Split("::");
- switch(argSplit[0])
+ switch (argSplit[0])
{
case "OutputFolderName":
{
- ofn = argSplit[1];
+ outputFolderPath = argSplit[1];
break;
}
- case "CompareFolderPath":
+ case "SourceFolderPath":
{
- cfp = argSplit[1];
+ sourceFolderPath = argSplit[1];
break;
}
case "TargetFolderPath":
{
- tfp = argSplit[1];
+ targetFolderPath = argSplit[1];
break;
}
case "AutoZip":
{
- az = bool.Parse(argSplit[1]);
+ autoZip = bool.Parse(argSplit[1]);
break;
}
}
}
- return new GenStartupArgs(ofn, cfp, tfp, az);
+ return new GenStartupArgs(outputFolderPath, sourceFolderPath, targetFolderPath, autoZip);
}
}
}
diff --git a/Patcher/PatchGenerator/Models/PatchGenInfo.cs b/Patcher/PatchGenerator/Models/PatchGenInfo.cs
new file mode 100644
index 0000000..5716350
--- /dev/null
+++ b/Patcher/PatchGenerator/Models/PatchGenInfo.cs
@@ -0,0 +1,66 @@
+using ReactiveUI;
+using System.IO;
+
+namespace PatchGenerator.Models
+{
+ public class PatchGenInfo : ReactiveObject
+ {
+ private void UpdateReadyToRun()
+ {
+ if (Directory.Exists(SourceFolderPath) && Directory.Exists(TargetFolderPath) && PatchName != "")
+ {
+ ReadyToRun = true;
+ return;
+ }
+
+ ReadyToRun = false;
+ }
+
+ private string _PatchName = "";
+ public string PatchName
+ {
+ get => _PatchName;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _PatchName, value);
+ UpdateReadyToRun();
+ }
+ }
+
+ private string _SourceFolderPath = "";
+ public string SourceFolderPath
+ {
+ get => _SourceFolderPath;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _SourceFolderPath, value);
+ UpdateReadyToRun();
+ }
+ }
+
+ private string _TargetFolderPath = "";
+ public string TargetFolderPath
+ {
+ get => _TargetFolderPath;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _TargetFolderPath, value);
+ UpdateReadyToRun();
+ }
+ }
+
+ private bool _AutoZip = true;
+ public bool AutoZip
+ {
+ get => _AutoZip;
+ set => this.RaiseAndSetIfChanged(ref _AutoZip, value);
+ }
+
+ private bool _ReadyToRun = false;
+ public bool ReadyToRun
+ {
+ get => _ReadyToRun;
+ set => this.RaiseAndSetIfChanged(ref _ReadyToRun, value);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/Models/PatchItem.cs b/Patcher/PatchGenerator/Models/PatchItem.cs
new file mode 100644
index 0000000..1355209
--- /dev/null
+++ b/Patcher/PatchGenerator/Models/PatchItem.cs
@@ -0,0 +1,40 @@
+using Avalonia.Media;
+using PatchGenerator.Helpers;
+using ReactiveUI;
+using System.Linq;
+
+namespace PatchGenerator.Models
+{
+ public class PatchItem : ReactiveObject
+ {
+ private string _Name = "";
+ public string Name
+ {
+ get => _Name;
+ set => this.RaiseAndSetIfChanged(ref _Name, value);
+ }
+
+ private IBrush _Color;
+ public IBrush Color
+ {
+ get => _Color;
+ set => this.RaiseAndSetIfChanged(ref _Color, value);
+ }
+
+ public PatchItem(string Name)
+ {
+ this.Name = Name.Replace(".new", "").Replace(".delta", "").Replace(".del", "");
+
+ IBrush color;
+
+ if (PatchItemDefinitions.Colors.TryGetValue(Name.Split('.').Last(), out color))
+ {
+ Color = color;
+ }
+ else
+ {
+ Color = PatchItemDefinitions.Colors["exists"];
+ }
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/Models/ViewNavigator.cs b/Patcher/PatchGenerator/Models/ViewNavigator.cs
new file mode 100644
index 0000000..9886f0b
--- /dev/null
+++ b/Patcher/PatchGenerator/Models/ViewNavigator.cs
@@ -0,0 +1,14 @@
+using ReactiveUI;
+
+namespace PatchGenerator.Models
+{
+ public class ViewNavigator : ReactiveObject
+ {
+ private object _SelectedViewModel;
+ public object SelectedViewModel
+ {
+ get => _SelectedViewModel;
+ set => this.RaiseAndSetIfChanged(ref _SelectedViewModel, value);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/PatchGenerator - Backup.csproj b/Patcher/PatchGenerator/PatchGenerator - Backup.csproj
new file mode 100644
index 0000000..140cb20
--- /dev/null
+++ b/Patcher/PatchGenerator/PatchGenerator - Backup.csproj
@@ -0,0 +1,43 @@
+
+
+ WinExe
+ net6.0
+ true
+ enable
+
+
+
+
+
+
+
+
+ References\Aki.Common.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename)
+
+
+
diff --git a/Patcher/PatchGenerator/PatchGenerator.csproj b/Patcher/PatchGenerator/PatchGenerator.csproj
index 3a3ee2e..1a7bea1 100644
--- a/Patcher/PatchGenerator/PatchGenerator.csproj
+++ b/Patcher/PatchGenerator/PatchGenerator.csproj
@@ -1,9 +1,9 @@
-
WinExe
- net5.0-windows
- true
+ net6.0
+ true
+ enable
@@ -16,4 +16,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename)
+
+
diff --git a/Patcher/PatchGenerator/Program.cs b/Patcher/PatchGenerator/Program.cs
new file mode 100644
index 0000000..dd81e50
--- /dev/null
+++ b/Patcher/PatchGenerator/Program.cs
@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+using System;
+
+namespace PatchGenerator
+{
+ class Program
+ {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace()
+ .UseReactiveUI();
+ }
+}
diff --git a/Patcher/PatchGenerator/Properties/launchSettings.json b/Patcher/PatchGenerator/Properties/launchSettings.json
new file mode 100644
index 0000000..112b9ec
--- /dev/null
+++ b/Patcher/PatchGenerator/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "PatchGenerator": {
+ "commandName": "Project",
+ "commandLineArgs": "\"OutputFolderName::Patcher_12.3.4.5_to_6.7.8.9\" \"SourceFolderPath::C:\\Users\\JohnO\\Desktop\\12.12.10.16338\" \"TargetFolderPath::C:\\Users\\JohnO\\Desktop\\12.11.7.15680\" \"AutoZip::True\"",
+ "workingDirectory": "C:\\Users\\JohnO\\Desktop\\Patcher\\"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Patcher/PatchGenerator/References/Aki.ByteBanger.dll b/Patcher/PatchGenerator/References/Aki.ByteBanger.dll
deleted file mode 100644
index b9fd681..0000000
Binary files a/Patcher/PatchGenerator/References/Aki.ByteBanger.dll and /dev/null differ
diff --git a/Patcher/PatchGenerator/References/ComponentAce.Compression.Libs.zlib.dll b/Patcher/PatchGenerator/References/ComponentAce.Compression.Libs.zlib.dll
deleted file mode 100644
index 52b6775..0000000
Binary files a/Patcher/PatchGenerator/References/ComponentAce.Compression.Libs.zlib.dll and /dev/null differ
diff --git a/Patcher/PatchGenerator/ViewLocator.cs b/Patcher/PatchGenerator/ViewLocator.cs
new file mode 100644
index 0000000..285ec15
--- /dev/null
+++ b/Patcher/PatchGenerator/ViewLocator.cs
@@ -0,0 +1,30 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using PatchGenerator.ViewModels;
+using System;
+
+namespace PatchGenerator
+{
+ public class ViewLocator : IDataTemplate
+ {
+ public IControl Build(object data)
+ {
+ var name = data.GetType().FullName!.Replace("ViewModel", "View");
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+ else
+ {
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+ }
+
+ public bool Match(object data)
+ {
+ return data is ViewModelBase;
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/ViewModels/MainWindowViewModel.cs b/Patcher/PatchGenerator/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..6395726
--- /dev/null
+++ b/Patcher/PatchGenerator/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,40 @@
+using Avalonia;
+using PatchGenerator.Models;
+using ReactiveUI;
+using Splat;
+using System.Windows.Input;
+
+namespace PatchGenerator.ViewModels
+{
+ public class MainWindowViewModel : ViewModelBase
+ {
+ public ICommand CloseCommand => ReactiveCommand.Create(() =>
+ {
+ if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
+ {
+ desktopApp.MainWindow.Close();
+ }
+ });
+
+ public ViewNavigator navigator { get; set; } = new ViewNavigator();
+ public MainWindowViewModel(GenStartupArgs genArgs = null)
+ {
+ Locator.CurrentMutable.RegisterConstant(navigator, typeof(ViewNavigator));
+
+ if (genArgs != null && genArgs.ReadyToRun)
+ {
+ PatchGenInfo genInfo = new PatchGenInfo();
+
+ genInfo.TargetFolderPath = genArgs.TargetFolderPath;
+ genInfo.SourceFolderPath = genArgs.SourceFolderPath;
+ genInfo.PatchName = genArgs.OutputFolderName;
+ genInfo.AutoZip = genArgs.AutoZip;
+
+ navigator.SelectedViewModel = new PatchGenerationViewModel(genInfo);
+ return;
+ }
+
+ navigator.SelectedViewModel = new OptionsViewModel();
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/ViewModels/OptionsViewModel.cs b/Patcher/PatchGenerator/ViewModels/OptionsViewModel.cs
new file mode 100644
index 0000000..d5f1f84
--- /dev/null
+++ b/Patcher/PatchGenerator/ViewModels/OptionsViewModel.cs
@@ -0,0 +1,23 @@
+using PatchGenerator.Models;
+using Splat;
+
+namespace PatchGenerator.ViewModels
+{
+ public class OptionsViewModel : ViewModelBase
+ {
+ public PatchGenInfo GenerationInfo { get; set; } = new PatchGenInfo();
+
+ private ViewNavigator navigator => Locator.Current.GetService();
+
+ public OptionsViewModel()
+ {
+ GenerationInfo.SourceFolderPath = "Drop SOURCE folder here";
+ GenerationInfo.TargetFolderPath = "Drop TARGET folder here";
+ }
+
+ public void GeneratePatches()
+ {
+ navigator.SelectedViewModel = new PatchGenerationViewModel(GenerationInfo);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs b/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs
new file mode 100644
index 0000000..821f041
--- /dev/null
+++ b/Patcher/PatchGenerator/ViewModels/PatchGenerationViewModel.cs
@@ -0,0 +1,132 @@
+using Avalonia;
+using Avalonia.Media;
+using PatcherUtils;
+using PatchGenerator.Helpers;
+using PatchGenerator.Models;
+using ReactiveUI;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PatchGenerator.ViewModels
+{
+ public class PatchGenerationViewModel : ViewModelBase
+ {
+ private bool _AutoScroll = true;
+ public bool AutoScroll
+ {
+ get => _AutoScroll;
+ set => this.RaiseAndSetIfChanged(ref _AutoScroll, value);
+ }
+
+ private string _ProgressMessage;
+ public string ProgressMessage
+ {
+ get => _ProgressMessage;
+ set => this.RaiseAndSetIfChanged(ref _ProgressMessage, value);
+ }
+
+ private int _PatchPercent;
+ public int PatchPercent
+ {
+ get => _PatchPercent;
+ set => this.RaiseAndSetIfChanged(ref _PatchPercent, value);
+ }
+
+ public ObservableCollection PatchItemCollection { get; set; } = new ObservableCollection();
+ public ObservableCollection PatchItemLegendCollection { get; set; } = new ObservableCollection();
+
+ private Stopwatch patchGenStopwatch = new Stopwatch();
+
+ private readonly PatchGenInfo generationInfo;
+ public PatchGenerationViewModel(PatchGenInfo GenerationInfo)
+ {
+ generationInfo = GenerationInfo;
+
+ foreach (KeyValuePair pair in PatchItemDefinitions.Colors)
+ {
+ PatchItemLegendCollection.Add(new PatchItem("")
+ {
+ Name = pair.Key,
+ Color = pair.Value,
+ });
+ }
+
+ GeneratePatches();
+ }
+
+ public void GeneratePatches()
+ {
+ Task.Run(() =>
+ {
+ //Slight delay to avoid some weird race condition in avalonia core, seems to be a bug, but also maybe I'm just stupid, idk -waffle
+ //Error without delay: An item with the same key has already been added. Key: [1, Avalonia.Controls.Generators.ItemContainerInfo]
+ System.Threading.Thread.Sleep(1000);
+
+ string patchOutputFolder = Path.Join(generationInfo.PatchName.FromCwd(), LazyOperations.PatchFolder);
+
+ PatchHelper patcher = new PatchHelper(generationInfo.SourceFolderPath, generationInfo.TargetFolderPath, patchOutputFolder);
+
+ patcher.ProgressChanged += Patcher_ProgressChanged;
+
+ patchGenStopwatch.Start();
+
+ patcher.GeneratePatches();
+
+ patchGenStopwatch.Stop();
+
+ StringBuilder sb = new StringBuilder()
+ .Append("Patches Generated in ")
+ .Append($"{patchGenStopwatch.Elapsed.Hours} hr/s ")
+ .Append($"{patchGenStopwatch.Elapsed.Minutes} min/s ")
+ .Append($"{patchGenStopwatch.Elapsed.Seconds} sec/s");
+
+ ProgressMessage = sb.ToString();
+
+ File.Copy(LazyOperations.PatcherClientPath, $"{generationInfo.PatchName.FromCwd()}\\patcher.exe", true);
+
+ if (generationInfo.AutoZip)
+ {
+ LazyOperations.StartZipProcess(generationInfo.PatchName.FromCwd(), $"{generationInfo.PatchName}.zip".FromCwd());
+ }
+ });
+ }
+
+ private void Patcher_ProgressChanged(object Sender, int Progress, int Total, int Percent, string Message = "", params LineItem[] AdditionalLineItems)
+ {
+ ProgressMessage = $"{Progress}/{Total}";
+
+ PatchPercent = Percent;
+
+ PatchItemCollection.Add(new PatchItem(Message));
+
+ if(Percent == 100)
+ {
+ if (Application.Current.Resources.TryGetResource("AKI_Brush_Yellow", out var color))
+ {
+ if (color is IBrush brush)
+ {
+ PatchItemCollection.Add(new PatchItem("")
+ {
+ Name = new StringBuilder().AppendLine("Summary").AppendLine("----------").ToString(),
+ Color = brush
+ });
+
+ foreach (LineItem item in AdditionalLineItems)
+ {
+ PatchItemCollection.Add(new PatchItem("")
+ {
+ Name = $"{item.ItemText}: {item.ItemValue}",
+ Color = brush
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/Patcher/PatchGenerator/ViewModels/ViewModelBase.cs b/Patcher/PatchGenerator/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..14a41bb
--- /dev/null
+++ b/Patcher/PatchGenerator/ViewModels/ViewModelBase.cs
@@ -0,0 +1,8 @@
+using ReactiveUI;
+
+namespace PatchGenerator.ViewModels
+{
+ public class ViewModelBase : ReactiveObject
+ {
+ }
+}
diff --git a/Patcher/PatchGenerator/Views/MainWindow.axaml b/Patcher/PatchGenerator/Views/MainWindow.axaml
new file mode 100644
index 0000000..4e9c849
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/MainWindow.axaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/Views/MainWindow.axaml.cs b/Patcher/PatchGenerator/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..ffc62a1
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/MainWindow.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PatchGenerator.Views
+{
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/Views/OptionsView.axaml b/Patcher/PatchGenerator/Views/OptionsView.axaml
new file mode 100644
index 0000000..68aa4e3
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/OptionsView.axaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/Views/OptionsView.axaml.cs b/Patcher/PatchGenerator/Views/OptionsView.axaml.cs
new file mode 100644
index 0000000..484cfad
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/OptionsView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PatchGenerator.Views
+{
+ public partial class OptionsView : UserControl
+ {
+ public OptionsView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/Patcher/PatchGenerator/Views/PatchGenerationView.axaml b/Patcher/PatchGenerator/Views/PatchGenerationView.axaml
new file mode 100644
index 0000000..fe5c2de
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/PatchGenerationView.axaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Patcher/PatchGenerator/Views/PatchGenerationView.axaml.cs b/Patcher/PatchGenerator/Views/PatchGenerationView.axaml.cs
new file mode 100644
index 0000000..958aa2f
--- /dev/null
+++ b/Patcher/PatchGenerator/Views/PatchGenerationView.axaml.cs
@@ -0,0 +1,32 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using PatchGenerator.AttachedProperties;
+
+namespace PatchGenerator.Views
+{
+ public partial class PatchGenerationView : UserControl
+ {
+ public PatchGenerationView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public void scrollChanged(object sender, ScrollChangedEventArgs e)
+ {
+ if (sender is ScrollViewer scrollViewer)
+ {
+ bool autoScroll = scrollViewer.GetValue(RandomBoolAttProp.RandomBoolProperty);
+
+ if (autoScroll)
+ {
+ scrollViewer.ScrollToEnd();
+ }
+ }
+ }
+ }
+}
diff --git a/Patcher/Patcher.sln b/Patcher/Patcher.sln
index 49f714d..bb6ffc8 100644
--- a/Patcher/Patcher.sln
+++ b/Patcher/Patcher.sln
@@ -1,13 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
-VisualStudioVersion = 16.0.31515.178
+VisualStudioVersion = 16.0.31727.386
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PatchGenerator", "PatchGenerator\PatchGenerator.csproj", "{DDB70566-994E-4884-8555-C005B238039B}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatchClient", "PatchClient\PatchClient.csproj", "{1C297369-03CA-4CA8-A651-22568A1CA310}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PatcherUtils", "PatcherUtils\PatcherUtils.csproj", "{A9819B34-8111-4344-B2B3-3DE5D7A43A45}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatchGenerator", "PatchGenerator\PatchGenerator.csproj", "{0E0664C4-E698-41E4-919F-892D627739EA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatchClient", "PatchClient\PatchClient.csproj", "{9CA4D2BD-6596-41D1-8354-135868DF8DAB}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatcherUtils", "PatcherUtils\PatcherUtils.csproj", "{C84233F8-6630-4322-9FB6-0A957F39CDA7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,23 +15,23 @@ Global
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
- {9CA4D2BD-6596-41D1-8354-135868DF8DAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9CA4D2BD-6596-41D1-8354-135868DF8DAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9CA4D2BD-6596-41D1-8354-135868DF8DAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9CA4D2BD-6596-41D1-8354-135868DF8DAB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C297369-03CA-4CA8-A651-22568A1CA310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C297369-03CA-4CA8-A651-22568A1CA310}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C297369-03CA-4CA8-A651-22568A1CA310}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C297369-03CA-4CA8-A651-22568A1CA310}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E0664C4-E698-41E4-919F-892D627739EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E0664C4-E698-41E4-919F-892D627739EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E0664C4-E698-41E4-919F-892D627739EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E0664C4-E698-41E4-919F-892D627739EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C84233F8-6630-4322-9FB6-0A957F39CDA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C84233F8-6630-4322-9FB6-0A957F39CDA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C84233F8-6630-4322-9FB6-0A957F39CDA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C84233F8-6630-4322-9FB6-0A957F39CDA7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {A9AA1062-33C6-49F2-880C-067D44041C4A}
+ SolutionGuid = {07AD7FB8-3529-4A2A-BDD5-76F779273B9B}
EndGlobalSection
EndGlobal
diff --git a/Patcher/PatcherUtils/LazyOperations.cs b/Patcher/PatcherUtils/LazyOperations.cs
index 06d4098..c4a69e8 100644
--- a/Patcher/PatcherUtils/LazyOperations.cs
+++ b/Patcher/PatcherUtils/LazyOperations.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Diagnostics;
+using System.Diagnostics;
using System.IO;
using System.Reflection;
@@ -67,9 +66,9 @@ namespace PatcherUtils
///
public static void PrepTempDir()
{
- foreach(string resource in Assembly.GetExecutingAssembly().GetManifestResourceNames())
+ foreach (string resource in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
- switch(resource)
+ switch (resource)
{
case string a when a.EndsWith(SevenZExe):
{
@@ -108,7 +107,7 @@ namespace PatcherUtils
{
DirectoryInfo dir = new DirectoryInfo(TempDir);
- if(dir.Exists)
+ if (dir.Exists)
{
dir.Delete(true);
}
diff --git a/Patcher/PatcherUtils/LineItem.cs b/Patcher/PatcherUtils/LineItem.cs
index 93dac23..b79cd43 100644
--- a/Patcher/PatcherUtils/LineItem.cs
+++ b/Patcher/PatcherUtils/LineItem.cs
@@ -3,10 +3,9 @@
public class LineItem
{
public string ItemText;
- public string ItemValue;
- public bool HasValue => ItemValue != "";
+ public int ItemValue;
- public LineItem(string ItemText, string ItemValue = "")
+ public LineItem(string ItemText, int ItemValue = 0)
{
this.ItemText = ItemText;
this.ItemValue = ItemValue;
diff --git a/Patcher/PatcherUtils/PatchHelper.cs b/Patcher/PatcherUtils/PatchHelper.cs
index 29395de..8722185 100644
--- a/Patcher/PatcherUtils/PatchHelper.cs
+++ b/Patcher/PatcherUtils/PatchHelper.cs
@@ -1,10 +1,9 @@
-using Aki.Common.Utils;
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Text;
+using System.Security.Cryptography;
namespace PatcherUtils
{
@@ -20,9 +19,14 @@ namespace PatcherUtils
private int deltaCount;
private int newCount;
private int delCount;
+ private int existCount;
private List AdditionalInfo = new List();
+ ///
+ /// Reports patch creation or application progress
+ ///
+ /// 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)
@@ -32,6 +36,13 @@ namespace PatcherUtils
ProgressChanged?.Invoke(this, progress, total, percent, Message, AdditionalLineItems);
}
+ ///
+ /// A helper class to create and apply patches to folders
+ ///
+ /// The directory that will have patches applied to it.
+ /// The directory to compare against during patch creation.
+ /// The directory where the patches are/will be located.
+ /// can be null if you only plan to apply patches.
public PatchHelper(string SourceFolder, string TargetFolder, string DeltaFolder)
{
this.SourceFolder = SourceFolder;
@@ -39,11 +50,42 @@ namespace PatcherUtils
this.DeltaFolder = DeltaFolder;
}
- private string GetDeltaPath(string sourceFile, string sourceFolder, string extension)
+ ///
+ /// Get the delta folder file path.
+ ///
+ ///
+ ///
+ /// The extension to append to the file
+ /// A file path inside the delta folder
+ private string GetDeltaPath(string SourceFilePath, string SourceFolderPath, string FileExtension)
{
- return Path.Join(DeltaFolder, $"{sourceFile.Replace(sourceFolder, "")}.{extension}");
+ return Path.Join(DeltaFolder, $"{SourceFilePath.Replace(SourceFolderPath, "")}.{FileExtension}");
}
+ ///
+ /// Check if two files have the same MD5 hash
+ ///
+ ///
+ ///
+ /// True if the hashes match
+ private bool CompareFileHashes(string SourceFilePath, string TargetFilePath)
+ {
+ using (MD5 md5Service = MD5.Create())
+ using (var sourceStream = File.OpenRead(SourceFilePath))
+ using (var targetStream = File.OpenRead(TargetFilePath))
+ {
+ byte[] sourceHash = md5Service.ComputeHash(sourceStream);
+ byte[] targetHash = md5Service.ComputeHash(targetStream);
+
+ return Enumerable.SequenceEqual(sourceHash, targetHash);
+ }
+ }
+
+ ///
+ /// Apply a delta to a file using xdelta
+ ///
+ ///
+ ///
private void ApplyDelta(string SourceFilePath, string DeltaFilePath)
{
string decodedPath = SourceFilePath + ".decoded";
@@ -56,20 +98,25 @@ namespace PatcherUtils
})
.WaitForExit();
- if(File.Exists(decodedPath))
+ if (File.Exists(decodedPath))
{
- File.Delete(SourceFilePath);
- File.Move(decodedPath, SourceFilePath);
+ File.Move(decodedPath, SourceFilePath, true);
}
}
+ ///
+ /// Create a .delta file using xdelta
+ ///
+ ///
+ ///
+ /// Used to patch an existing file with xdelta
private void CreateDelta(string SourceFilePath, string TargetFilePath)
{
FileInfo sourceFileInfo = new FileInfo(SourceFilePath);
string deltaPath = GetDeltaPath(SourceFilePath, SourceFolder, "delta");
- Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name+".delta", ""));
+ Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name + ".delta", ""));
//TODO - don't hardcode FileName
@@ -82,28 +129,43 @@ namespace PatcherUtils
.WaitForExit();
}
+ ///
+ /// Create a .del file
+ ///
+ ///
+ /// Used to mark a file for deletion
private void CreateDelFile(string SourceFile)
{
FileInfo sourceFileInfo = new FileInfo(SourceFile);
string deltaPath = GetDeltaPath(SourceFile, SourceFolder, "del");
- Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name+".del", ""));
+ Directory.CreateDirectory(deltaPath.Replace(sourceFileInfo.Name + ".del", ""));
File.Create(deltaPath);
}
+ ///
+ /// Create a .new file
+ ///
+ ///
+ /// Used to mark a file that needs to be added
private void CreateNewFile(string TargetFile)
{
FileInfo targetSourceInfo = new FileInfo(TargetFile);
string deltaPath = GetDeltaPath(TargetFile, TargetFolder, "new");
- Directory.CreateDirectory(deltaPath.Replace(targetSourceInfo.Name+".new", ""));
+ Directory.CreateDirectory(deltaPath.Replace(targetSourceInfo.Name + ".new", ""));
targetSourceInfo.CopyTo(deltaPath, true);
}
+ ///
+ /// Generate a full set of patches using the source and target folders specified during contruction./>
+ ///
+ ///
+ /// Patches are created in the delta folder specified during contruction
public bool GeneratePatches()
{
//get all directory information needed
@@ -118,14 +180,18 @@ namespace PatcherUtils
return false;
}
+ LazyOperations.CleanupTempDir();
+ LazyOperations.PrepTempDir();
+
List SourceFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories).ToList();
fileCountTotal = SourceFiles.Count;
AdditionalInfo.Clear();
- AdditionalInfo.Add(new LineItem("Delta Patch", "0"));
- AdditionalInfo.Add(new LineItem("New Patch", "0"));
- AdditionalInfo.Add(new LineItem("Del Patch", "0"));
+ AdditionalInfo.Add(new LineItem("Delta Patch", 0));
+ AdditionalInfo.Add(new LineItem("New Patch", 0));
+ AdditionalInfo.Add(new LineItem("Del Patch", 0));
+ AdditionalInfo.Add(new LineItem("File Exists", 0));
filesProcessed = 0;
@@ -144,23 +210,34 @@ namespace PatcherUtils
newCount++;
filesProcessed++;
- RaiseProgressChanged(filesProcessed, fileCountTotal, targetFile.Name, AdditionalInfo.ToArray());
+ RaiseProgressChanged(filesProcessed, fileCountTotal, $"{targetFile.FullName.Replace(TargetFolder, "...")}.new", AdditionalInfo.ToArray());
continue;
}
- //if a matching source file was found, get the delta for it.
- CreateDelta(sourceFile.FullName, targetFile.FullName);
+ string extension = "";
+
+ //if a matching source file was found, check the file hashes and get the delta.
+ if (!CompareFileHashes(sourceFile.FullName, targetFile.FullName))
+ {
+ CreateDelta(sourceFile.FullName, targetFile.FullName);
+ extension = ".delta";
+ deltaCount++;
+ }
+ else
+ {
+ existCount++;
+ }
SourceFiles.Remove(sourceFile);
- deltaCount++;
filesProcessed++;
- AdditionalInfo[0].ItemValue = deltaCount.ToString();
- AdditionalInfo[1].ItemValue = newCount.ToString();
+ AdditionalInfo[0].ItemValue = deltaCount;
+ AdditionalInfo[1].ItemValue = newCount;
+ AdditionalInfo[3].ItemValue = existCount;
- RaiseProgressChanged(filesProcessed, fileCountTotal, targetFile.Name, AdditionalInfo.ToArray());
+ RaiseProgressChanged(filesProcessed, fileCountTotal, $"{targetFile.FullName.Replace(TargetFolder, "...")}{extension}", AdditionalInfo.ToArray());
}
//Any remaining source files do not exist in the target folder and can be removed.
@@ -175,15 +252,19 @@ namespace PatcherUtils
delCount++;
- AdditionalInfo[2].ItemValue = delCount.ToString();
+ AdditionalInfo[2].ItemValue = delCount;
filesProcessed++;
- RaiseProgressChanged(filesProcessed, fileCountTotal, "", AdditionalInfo.ToArray());
+ RaiseProgressChanged(filesProcessed, fileCountTotal, $"{delFile.FullName.Replace(SourceFolder, "...")}.del", AdditionalInfo.ToArray());
}
return true;
}
+ ///
+ /// Apply a set of patches using the source and delta folders specified during construction.
+ ///
+ ///
public string ApplyPatches()
{
//get needed directory information
@@ -196,6 +277,9 @@ namespace PatcherUtils
return "One of the supplied directories doesn't exist";
}
+ LazyOperations.CleanupTempDir();
+ LazyOperations.PrepTempDir();
+
List SourceFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories).ToList();
List deltaFiles = deltaDir.GetFiles("*", SearchOption.AllDirectories).ToList();
@@ -207,9 +291,9 @@ namespace PatcherUtils
AdditionalInfo = new List()
{
- new LineItem("Patches Remaining", deltaCount.ToString()),
- new LineItem("New Files to Add", newCount.ToString()),
- new LineItem("Files to Delete", delCount.ToString())
+ new LineItem("Patches Remaining", deltaCount),
+ new LineItem("New Files to Add", newCount),
+ new LineItem("Files to Delete", delCount)
};
filesProcessed = 0;
@@ -218,14 +302,14 @@ namespace PatcherUtils
foreach (FileInfo deltaFile in deltaDir.GetFiles("*", SearchOption.AllDirectories))
{
- switch(deltaFile.Extension)
+ 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)
+ if (sourceFile == null)
{
return $"Failed to find matching source file for '{deltaFile.FullName}'";
}
@@ -238,10 +322,15 @@ namespace PatcherUtils
}
case ".new":
{
+ if (newCount == 2 || newCount == 1 || newCount == 0)
+ {
+
+ }
+
//copy new file
string destination = Path.Join(sourceDir.FullName, deltaFile.FullName.Replace(deltaDir.FullName, "").Replace(".new", ""));
- File.Copy(deltaFile.FullName, destination);
+ File.Copy(deltaFile.FullName, destination, true);
newCount--;
@@ -260,9 +349,9 @@ namespace PatcherUtils
}
}
- AdditionalInfo[0].ItemValue = deltaCount.ToString();
- AdditionalInfo[1].ItemValue = newCount.ToString();
- AdditionalInfo[2].ItemValue = delCount.ToString();
+ AdditionalInfo[0].ItemValue = deltaCount;
+ AdditionalInfo[1].ItemValue = newCount;
+ AdditionalInfo[2].ItemValue = delCount;
++filesProcessed;
RaiseProgressChanged(filesProcessed, fileCountTotal, deltaFile.Name, AdditionalInfo.ToArray());
diff --git a/Patcher/PatcherUtils/PatcherUtils.csproj b/Patcher/PatcherUtils/PatcherUtils.csproj
index 8e7ab05..3ca61a5 100644
--- a/Patcher/PatcherUtils/PatcherUtils.csproj
+++ b/Patcher/PatcherUtils/PatcherUtils.csproj
@@ -1,7 +1,7 @@
-
+
- net5.0
+ net6.0
diff --git a/Patcher/PatcherUtils/Resources/PatchClient.exe b/Patcher/PatcherUtils/Resources/PatchClient.exe
index 8338438..1c4bc9d 100644
Binary files a/Patcher/PatcherUtils/Resources/PatchClient.exe and b/Patcher/PatcherUtils/Resources/PatchClient.exe differ
diff --git a/README.md b/README.md
index fd55334..46d8319 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
# Patcher
-Allows for generating and applying patches to software.
-Currently used for downgrading EFT.
\ No newline at end of file
+Allows for generating and applying patches to software.
+Currently used for downgrading EFT.
+
+## Requirements
+- .net 6
+
+## Development Stuff
+- VS 2022 w/ Avalonia VS Extension
\ No newline at end of file