diff --git a/EftPatchHelper/EftPatchHelper.sln b/EftPatchHelper/EftPatchHelper.sln
new file mode 100644
index 0000000..08b7341
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32126.317
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EftPatchHelper", "EftPatchHelper\EftPatchHelper.csproj", "{53238CBB-729D-4E9B-ABB3-12C430AE56F1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {53238CBB-729D-4E9B-ABB3-12C430AE56F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53238CBB-729D-4E9B-ABB3-12C430AE56F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53238CBB-729D-4E9B-ABB3-12C430AE56F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53238CBB-729D-4E9B-ABB3-12C430AE56F1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D08D2C5F-8AAA-4A57-AD66-F21402682B0C}
+ EndGlobalSection
+EndGlobal
diff --git a/EftPatchHelper/EftPatchHelper/EftPatchHelper.csproj b/EftPatchHelper/EftPatchHelper/EftPatchHelper.csproj
new file mode 100644
index 0000000..95b62d7
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper/EftPatchHelper.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/EftPatchHelper/EftPatchHelper/Helpers/FolderCleaner.cs b/EftPatchHelper/EftPatchHelper/Helpers/FolderCleaner.cs
new file mode 100644
index 0000000..321b029
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper/Helpers/FolderCleaner.cs
@@ -0,0 +1,61 @@
+using Spectre.Console;
+
+namespace EftPatchHelper.Helpers
+{
+ public static class FolderCleaner
+ {
+ public static string cleanPathsFile = Path.Join(Directory.GetCurrentDirectory(), "removePaths.txt");
+
+ public static string[] AssumedPaths =
+ {
+ "BattlEye",
+ "cache",
+ "Logs",
+ "ConsistencyInfo",
+ "EscapeFromTarkov_BE.exe",
+ "Uninstall.exe",
+ "UnityCrashHandler64.exe",
+ "WinPixEventRuntime.dll"
+ };
+
+ public static void Clean(string FolderPath)
+ {
+ AnsiConsole.Status()
+ .Spinner(Spinner.Known.Default)
+ .Start($"Cleaning Folder ...", ctx =>
+ {
+ AnsiConsole.MarkupLine($"[blue]INFO:[/] [gray]Getting folders to remove for {FolderPath} ...[/]");
+ if (!File.Exists(cleanPathsFile)) File.WriteAllLines(cleanPathsFile, AssumedPaths);
+
+ string[] delPaths = File.ReadAllLines(cleanPathsFile);
+
+ foreach (string delPath in delPaths)
+ {
+ string fsItemToRemove = Path.Join(FolderPath, delPath);
+
+ FileSystemInfo fsInfo = Directory.Exists(fsItemToRemove) ? new DirectoryInfo(fsItemToRemove) : new FileInfo(fsItemToRemove);
+
+ if (fsInfo is DirectoryInfo dInfo)
+ {
+ dInfo.Delete(true);
+ }
+ else
+ {
+ fsInfo.Delete();
+ }
+
+
+ fsInfo.Refresh();
+
+ if(!fsInfo.Exists)
+ {
+ AnsiConsole.MarkupLine($"[blue]INFO:[/] [gray]Deleting {fsInfo.Name} ...[/] [green]OK[/]");
+ continue;
+ }
+
+ AnsiConsole.MarkupLine($"[blue]INFO:[/] [gray]Deleting {fsInfo.Name} ...[/] [red]Failed[/]");
+ }
+ });
+ }
+ }
+}
diff --git a/EftPatchHelper/EftPatchHelper/Helpers/FolderCopy.cs b/EftPatchHelper/EftPatchHelper/Helpers/FolderCopy.cs
new file mode 100644
index 0000000..1d4e974
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper/Helpers/FolderCopy.cs
@@ -0,0 +1,86 @@
+using Spectre.Console;
+
+namespace EftPatchHelper.Helpers
+{
+ public class FolderCopy
+ {
+ private string SourceFolder { get; }
+ private string DestinationFolder { get; }
+
+ ///
+ /// Create a folder copy object
+ ///
+ /// The folder to copy
+ /// The folder to copy into
+ public FolderCopy(string SourceFolder, string DestinationFolder)
+ {
+ this.SourceFolder = SourceFolder;
+ this.DestinationFolder = DestinationFolder;
+ }
+
+ public bool Start()
+ {
+ DirectoryInfo sourceDir = new DirectoryInfo(SourceFolder);
+ DirectoryInfo destDir = new DirectoryInfo(DestinationFolder);
+
+ if (!destDir.Exists)
+ {
+ destDir.Create();
+ destDir.Refresh();
+ }
+ else
+ {
+ if(!AnsiConsole.Confirm($"{destDir.FullName} exists. Do you want to overwright it?", false))
+ {
+ AnsiConsole.MarkupLine("[yellow]Using existing folder[/]");
+ return true;
+ }
+
+ destDir.Delete(true);
+ destDir.Create();
+ destDir.Refresh();
+ }
+
+ string[] files = Directory.GetFiles(this.SourceFolder, "*", SearchOption.AllDirectories);
+
+ AnsiConsole.Progress()
+ .Columns(new ProgressColumn[]
+ {
+ new TaskDescriptionColumn(),
+ new ProgressBarColumn(),
+ new PercentageColumn(),
+ new ElapsedTimeColumn(),
+ new SpinnerColumn()
+ })
+ .Start(ctx =>
+ {
+ var copyTask = ctx.AddTask($"Copying [green]{sourceDir.Name}[/] -> [green]{destDir.Parent?.Name ?? destDir.Name}[/]");
+
+ copyTask.MaxValue = files.Count();
+
+ foreach (string file in files)
+ {
+ FileInfo sourceFileInfo = new FileInfo(file);
+
+ string relativeDestParentPath = sourceFileInfo.DirectoryName?.Replace(sourceDir.FullName, "") ??
+ throw new Exception($"Failed to get destination file path for {sourceFileInfo.FullName}");
+
+ DirectoryInfo destParent = new DirectoryInfo(Path.Join(destDir.FullName, relativeDestParentPath));
+
+ if (!destParent.Exists)
+ {
+ destParent.Create();
+ }
+
+ string targetFile = Path.Join(destParent.FullName, sourceFileInfo.Name);
+
+ sourceFileInfo.CopyTo(targetFile, true);
+
+ copyTask.Increment(1);
+ }
+ });
+
+ return true;
+ }
+ }
+}
diff --git a/EftPatchHelper/EftPatchHelper/Program.cs b/EftPatchHelper/EftPatchHelper/Program.cs
new file mode 100644
index 0000000..9be5695
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper/Program.cs
@@ -0,0 +1,141 @@
+// See https://aka.ms/new-console-template for more information
+using EftPatchHelper;
+using EftPatchHelper.Helpers;
+using Spectre.Console;
+using System.Diagnostics;
+
+Settings? settings = Settings.Load();
+
+// check settings file exists
+if(settings == null)
+{
+ settings = new Settings();
+ settings.Save();
+
+ AnsiConsole.MarkupLine($"Settings file was create here: \n[blue]{Settings.settingsFile}[/]\n\nPlease update it and try again.");
+ return;
+}
+
+// validate settings
+if(!settings.Validate())
+{
+ AnsiConsole.MarkupLine($"[red]Settings file seems to be missing some information, please fix it[/]\n\nPath to file:\n[blue]{Settings.settingsFile}[/]\n\n");
+ AnsiConsole.MarkupLine("Press [blue]Enter[/] to close ...");
+ return;
+}
+
+/// Fancy
+AnsiConsole.Write(new FigletText("EFT Patch Helper").Centered().Color(Color.Blue));
+
+// show some settings information
+AnsiConsole.WriteLine();
+AnsiConsole.MarkupLine($"Current target version is [purple]{settings.TargetEftVersion}[/]");
+AnsiConsole.MarkupLine($"Prep folder path is [purple]{settings.PrepFolderPath}[/]");
+AnsiConsole.MarkupLine($"Backup folder path is [purple]{settings.BackupFolderPath}[/]");
+AnsiConsole.WriteLine();
+
+// Source Selection Prompt
+SelectionPrompt sourcePrompt = new SelectionPrompt()
+{
+ Title = "Select Source Version",
+ MoreChoicesText = "Move cursor to see more versions",
+ PageSize = 10
+};
+
+// Get eft live version
+var eftVersion = FileVersionInfo.GetVersionInfo(Path.Join(settings.LiveEftPath, "EscapeFromTarkov.exe")).ProductVersion?.Replace('-', '.');
+
+if(eftVersion != null)
+{
+ //remove leading 0 from version number
+ if (eftVersion.StartsWith("0."))
+ {
+ eftVersion = eftVersion.Remove(0, 2);
+ }
+
+ // add eft liver version to selection prompt choices
+ sourcePrompt.AddChoice($"Live: {eftVersion}");
+}
+
+// add backup folders to source prompt choices
+foreach(string backup in Directory.GetDirectories(settings.BackupFolderPath))
+{
+ DirectoryInfo backupDir = new DirectoryInfo(backup);
+
+ if(!backupDir.Exists)
+ {
+ continue;
+ }
+
+ sourcePrompt.AddChoice($"Backup: {backupDir.Name}");
+}
+
+
+string result = sourcePrompt.Show(AnsiConsole.Console);
+
+string sourceVersion = result.Split(": ")[1];
+
+string sourceBackupPath = Path.Join(settings.BackupFolderPath, sourceVersion);
+
+//backup live folder if it was selected
+if(result.StartsWith("Live:"))
+{
+ // only backup the live folder if it doesn't exist already
+ if(!Directory.Exists(sourceBackupPath))
+ {
+ AnsiConsole.MarkupLine("[blue]Backing up live ...[/]");
+ FolderCopy backupLiveCopy = new FolderCopy(settings.LiveEftPath, sourceBackupPath);
+
+ backupLiveCopy.Start();
+ }
+}
+
+string targetBackupPath = Path.Join(settings.BackupFolderPath, settings.TargetEftVersion);
+
+string targetPrepPath = Path.Join(settings.PrepFolderPath, settings.TargetEftVersion);
+
+string sourcePrepPath = Path.Join(settings.PrepFolderPath, sourceVersion);
+
+
+//copy source to prep directory
+AnsiConsole.MarkupLine("[gray]Copying[/] [blue]source[/][gray] to prep area ...[/]");
+
+FolderCopy sourceCopy = new FolderCopy(sourceBackupPath, sourcePrepPath);
+
+sourceCopy.Start();
+
+//copy target to prep directory
+AnsiConsole.MarkupLine("[gray]Copying[/] [blue]target[/][gray] to prep area ...[/]");
+
+FolderCopy targetCopy = new FolderCopy(targetBackupPath, targetPrepPath);
+
+targetCopy.Start();
+
+// clean prep source and target folders of uneeded data
+FolderCleaner.Clean(sourcePrepPath);
+
+FolderCleaner.Clean(targetPrepPath);
+
+// start patcher
+if(File.Exists(settings.PatcherEXEPath))
+{
+ string patcherOutputName = $"Patcher_{sourceVersion}_to_{settings.TargetEftVersion}";
+
+ AnsiConsole.Markup("Starting patcher ... ");
+
+ Process.Start(new ProcessStartInfo()
+ {
+ FileName = settings.PatcherEXEPath,
+ WorkingDirectory = new FileInfo(settings.PatcherEXEPath).Directory?.FullName ?? Directory.GetCurrentDirectory(),
+ ArgumentList = {$"OutputFolderName::{patcherOutputName}", $"SourceFolderPath::{sourcePrepPath}", $"TargetFolderPath::{targetPrepPath}", $"AutoZip::{settings.AutoZip}"}
+ });
+}
+
+AnsiConsole.MarkupLine("[green]done[/]");
+
+AnsiConsole.WriteLine();
+
+// done
+AnsiConsole.MarkupLine("Press [blue]Enter[/] to close ...");
+
+Console.ReadLine();
\ No newline at end of file
diff --git a/EftPatchHelper/EftPatchHelper/Settings.cs b/EftPatchHelper/EftPatchHelper/Settings.cs
new file mode 100644
index 0000000..6feb2ed
--- /dev/null
+++ b/EftPatchHelper/EftPatchHelper/Settings.cs
@@ -0,0 +1,62 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace EftPatchHelper
+{
+ public class Settings
+ {
+ [JsonIgnore]
+ public static string settingsFile = Path.Join(Directory.GetCurrentDirectory(), "settings.json");
+
+ [JsonPropertyName("target_eft_version")]
+ public string TargetEftVersion { get; set; } = "";
+
+ [JsonPropertyName("prep_folder_path")]
+ public string PrepFolderPath { get; set; } = "";
+
+ [JsonPropertyName("backup_folder_path")]
+ public string BackupFolderPath { get; set; } = "";
+
+ [JsonPropertyName("live_eft_path")]
+ public string LiveEftPath { get; set; } = "";
+
+ [JsonPropertyName("auto_zip")]
+ public bool AutoZip { get; set; } = true;
+
+ [JsonPropertyName("patcher_exe_path")]
+ public string PatcherEXEPath { get; set; } = "";
+
+ public void Save()
+ {
+ string json = JsonSerializer.Serialize(this, typeof(Settings), new JsonSerializerOptions() { WriteIndented = true });
+
+ if (string.IsNullOrWhiteSpace(json)) return;
+
+ File.WriteAllText(settingsFile, json);
+ }
+
+ public static Settings? Load()
+ {
+ if (!File.Exists(settingsFile)) return null;
+
+ string json = File.ReadAllText(settingsFile);
+
+ return JsonSerializer.Deserialize(json);
+ }
+
+ public bool Validate()
+ {
+ if(string.IsNullOrWhiteSpace(TargetEftVersion)) return false;
+
+ if(string.IsNullOrWhiteSpace(PrepFolderPath)) return false;
+
+ if(string.IsNullOrWhiteSpace(BackupFolderPath)) return false;
+
+ if(string.IsNullOrWhiteSpace(LiveEftPath)) return false;
+
+ if(string.IsNullOrWhiteSpace(PatcherEXEPath)) return false;
+
+ return true;
+ }
+ }
+}
\ No newline at end of file