Merge pull request 'Update Installer' (#7) from waffle.lord/SPT-AKI-Installer:master into master
Reviewed-on: CWX/SPT-AKI-Installer#7
This commit is contained in:
commit
7ff99677ed
17
Aki.Core/Interfaces/ILiveTaskTableEntry.cs
Normal file
17
Aki.Core/Interfaces/ILiveTaskTableEntry.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Spectre.Console;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Interfaces
|
||||
{
|
||||
public interface ILiveTaskTableEntry
|
||||
{
|
||||
public string TaskName { get; set; }
|
||||
|
||||
public int RowIndex { get; set; }
|
||||
|
||||
public void SetContext(LiveDisplayContext context, Table table);
|
||||
|
||||
public Task<GenericResult> RunAsync();
|
||||
}
|
||||
}
|
7
Aki.Core/Interfaces/IProgressableTask.cs
Normal file
7
Aki.Core/Interfaces/IProgressableTask.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace SPT_AKI_Installer.Aki.Core.Interfaces
|
||||
{
|
||||
internal interface IProgressableTask
|
||||
{
|
||||
public int Progress { get; set; }
|
||||
}
|
||||
}
|
53
Aki.Core/InternalData.cs
Normal file
53
Aki.Core/InternalData.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core
|
||||
{
|
||||
public class InternalData
|
||||
{
|
||||
/// <summary>
|
||||
/// The folder to install SPT into
|
||||
/// </summary>
|
||||
public string TargetInstallPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The orginal EFT game path
|
||||
/// </summary>
|
||||
public string OriginalGamePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original EFT game version
|
||||
/// </summary>
|
||||
public string OriginalGameVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Patcher zip file info
|
||||
/// </summary>
|
||||
public FileInfo PatcherZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SPT-AKI zip file info
|
||||
/// </summary>
|
||||
public FileInfo AkiZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release download link for SPT-AKI
|
||||
/// </summary>
|
||||
public string AkiReleaseDownloadLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release download link for the patcher mirror list
|
||||
/// </summary>
|
||||
public string PatcherMirrorsLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release download mirrors for the patcher
|
||||
/// </summary>
|
||||
public List<string> PatcherReleaseMirrors { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a patch is needed to downgrade the client files
|
||||
/// </summary>
|
||||
public bool PatchNeeded { get; set; }
|
||||
}
|
||||
}
|
18
Aki.Core/Model/GenericResult.cs
Normal file
18
Aki.Core/Model/GenericResult.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
||||
{
|
||||
public class GenericResult
|
||||
{
|
||||
public string Message { get; private set; }
|
||||
|
||||
public bool Succeeded { get; private set; }
|
||||
|
||||
protected GenericResult(string message, bool succeeded)
|
||||
{
|
||||
Message = message;
|
||||
Succeeded = succeeded;
|
||||
}
|
||||
|
||||
public static GenericResult FromSuccess(string message = "") => new GenericResult(message, true);
|
||||
public static GenericResult FromError(string errorMessage) => new GenericResult(errorMessage, false);
|
||||
}
|
||||
}
|
159
Aki.Core/Model/LiveTableTask.cs
Normal file
159
Aki.Core/Model/LiveTableTask.cs
Normal file
@ -0,0 +1,159 @@
|
||||
using Spectre.Console;
|
||||
using SPT_AKI_Installer.Aki.Core.Interfaces;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
||||
{
|
||||
public abstract class LiveTableTask : ILiveTaskTableEntry, IProgressableTask, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The name that will be displayed in th first column of the table
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Wheather the task reports progress or not
|
||||
/// </summary>
|
||||
public bool IsIndeterminate;
|
||||
|
||||
/// <summary>
|
||||
/// The progress (percent completed) of the task
|
||||
/// </summary>
|
||||
public int Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The row index in the table of the task
|
||||
/// </summary>
|
||||
public int RowIndex { get; set; }
|
||||
|
||||
private bool _continueRenderingProgress = false;
|
||||
private bool _continueRenderingIndeterminateProgress = false;
|
||||
|
||||
private int _indeterminateState = 0;
|
||||
|
||||
private string _currentStatus = "running";
|
||||
|
||||
private Table _table { get; set; }
|
||||
|
||||
private LiveDisplayContext _context { get; set; }
|
||||
|
||||
public LiveTableTask(string name, bool isIndeterminate = true)
|
||||
{
|
||||
TaskName = name;
|
||||
IsIndeterminate = isIndeterminate;
|
||||
}
|
||||
|
||||
private string GetIndetermminateStatus()
|
||||
{
|
||||
string status = $"[blue]{_currentStatus.EscapeMarkup()} ";
|
||||
|
||||
if (_indeterminateState > 3) _indeterminateState = 0;
|
||||
|
||||
status += new string('.', _indeterminateState);
|
||||
|
||||
status += "[/]";
|
||||
|
||||
_indeterminateState++;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start indeterminate progress spinner
|
||||
/// </summary>
|
||||
/// <remarks>this doesn't need to be called if you set isIndeterminate in the constructor. You need to set IsIndeterminate to false to stop this background task</remarks>
|
||||
public void StartDrawingIndeterminateProgress()
|
||||
{
|
||||
_continueRenderingProgress = false;
|
||||
_continueRenderingIndeterminateProgress = true;
|
||||
|
||||
new Task(new Action(() => { RenderIndeterminateProgress(ref _continueRenderingIndeterminateProgress); })).Start();
|
||||
}
|
||||
|
||||
public void StartDrawingProgress()
|
||||
{
|
||||
Progress = 0;
|
||||
_continueRenderingIndeterminateProgress = false;
|
||||
_continueRenderingProgress = true;
|
||||
|
||||
new Task(new Action(() => { RenderProgress(ref _continueRenderingProgress); })).Start();
|
||||
}
|
||||
|
||||
private void ReRenderEntry(string message)
|
||||
{
|
||||
_table.RemoveRow(RowIndex);
|
||||
_table.InsertRow(RowIndex, TaskName, message);
|
||||
_context.Refresh();
|
||||
}
|
||||
|
||||
private void RenderIndeterminateProgress(ref bool continueRendering)
|
||||
{
|
||||
while (continueRendering)
|
||||
{
|
||||
ReRenderEntry(GetIndetermminateStatus());
|
||||
Thread.Sleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderProgress(ref bool continueRendering)
|
||||
{
|
||||
while (continueRendering)
|
||||
{
|
||||
string progressBar = new string(' ', 10);
|
||||
|
||||
int progressFill = (int)Math.Floor((double)Progress / 10);
|
||||
|
||||
progressBar = progressBar.Remove(0, progressFill).Insert(0, new string('=', progressFill));
|
||||
|
||||
progressBar = $"[blue][[{progressBar}]][/] {Progress}% {_currentStatus}";
|
||||
|
||||
ReRenderEntry(progressBar);
|
||||
|
||||
Thread.Sleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the context and table for this task
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="table"></param>
|
||||
/// <remarks>This is called by <see cref="LiveTableTaskRunner"/> when it is ran. No need to call it yourself</remarks>
|
||||
public void SetContext(LiveDisplayContext context, Table table)
|
||||
{
|
||||
_context = context;
|
||||
_table = table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the status text for the task
|
||||
/// </summary>
|
||||
/// <param name="message">The message to show</param>
|
||||
/// <param name="stopRendering">Stop rendering progress updates (progress and indeterminate progress tasks)</param>
|
||||
/// <remarks>If you are running an indeterminate task, set render to false. It will render at the next indeterminate update interval</remarks>
|
||||
public void SetStatus(string message, bool stopRendering = true)
|
||||
{
|
||||
_currentStatus = message;
|
||||
|
||||
if (stopRendering)
|
||||
{
|
||||
_continueRenderingProgress = false;
|
||||
_continueRenderingIndeterminateProgress = false;
|
||||
ReRenderEntry(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the task async
|
||||
/// </summary>
|
||||
/// <returns>Returns the result of the task</returns>
|
||||
public abstract Task<GenericResult> RunAsync();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsIndeterminate = false;
|
||||
}
|
||||
}
|
||||
}
|
78
Aki.Core/Model/LiveTableTaskRunner.cs
Normal file
78
Aki.Core/Model/LiveTableTaskRunner.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
||||
{
|
||||
public class LiveTableTaskRunner
|
||||
{
|
||||
private static async Task<(bool, LiveTableTask task)> RunAllTasksAsync(List<LiveTableTask> tasks, LiveDisplayContext context, Table table)
|
||||
{
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
if (task.IsIndeterminate)
|
||||
{
|
||||
task.StartDrawingIndeterminateProgress();
|
||||
}
|
||||
else
|
||||
{
|
||||
task.StartDrawingProgress();
|
||||
}
|
||||
|
||||
var result = await task.RunAsync();
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
task.SetStatus($"[red]{result.Message.EscapeMarkup()}[/]");
|
||||
return (false, task);
|
||||
}
|
||||
|
||||
task.SetStatus($"[green]{(result.Message == "" ? "Complete" : $"{result.Message.EscapeMarkup()}")}[/]");
|
||||
}
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
public static async Task RunAsync(List<LiveTableTask> tasks)
|
||||
{
|
||||
int halfBufferWidth = Console.BufferWidth / 2;
|
||||
|
||||
Table table = new Table().Alignment(Justify.Center).Border(TableBorder.Rounded).BorderColor(Color.Grey).AddColumn("Task", (column) =>
|
||||
{
|
||||
column.Width(halfBufferWidth);
|
||||
})
|
||||
.AddColumn("Status", (column) =>
|
||||
{
|
||||
column.Width(halfBufferWidth);
|
||||
});
|
||||
|
||||
await AnsiConsole.Live(table).StartAsync(async context =>
|
||||
{
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
table.AddRow(task.TaskName, "[purple]Pending[/]");
|
||||
task.RowIndex = table.Rows.Count() - 1;
|
||||
task.SetContext(context, table);
|
||||
|
||||
await Task.Delay(50);
|
||||
context.Refresh();
|
||||
}
|
||||
|
||||
var result = await RunAllTasksAsync(tasks, context, table);
|
||||
|
||||
// if a task failed and returned early, set any remaining task status to cancelled
|
||||
if (!result.Item1)
|
||||
{
|
||||
var cancelledTasks = tasks.Take(new Range(tasks.IndexOf(result.Item2) + 1, tasks.Count));
|
||||
|
||||
foreach (var task in cancelledTasks)
|
||||
{
|
||||
task.SetStatus("[grey]Cancelled[/]");
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
using Spectre.Console;
|
||||
using SPT_AKI_Installer.Aki.Helper;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core
|
||||
{
|
||||
//TODO:
|
||||
// locales, language selection
|
||||
|
||||
public static class SPTinstaller
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
string targetPath = Environment.CurrentDirectory;
|
||||
#if DEBUG
|
||||
targetPath = @"D:\install";
|
||||
#endif
|
||||
SpectreHelper.Figlet("SPT-AKI INSTALLER", Color.Yellow);
|
||||
PreCheckHelper.GameCheck(out string originalGamePath);
|
||||
PreCheckHelper.DetectOriginalGameVersion(originalGamePath);
|
||||
|
||||
if (originalGamePath == null)
|
||||
{
|
||||
CloseApp("Unable to find EFT OG directory \n please make sure EFT is installed \n please also run EFT once");
|
||||
}
|
||||
|
||||
if (originalGamePath == targetPath)
|
||||
{
|
||||
CloseApp("Installer is located in EFT's original directory \n Please move the installer to a seperate folder as per the guide");
|
||||
}
|
||||
|
||||
var checkForExistingFiles = FileHelper.FindFile(targetPath, "EscapeFromTarkov.exe");
|
||||
if (checkForExistingFiles != null)
|
||||
{
|
||||
CloseApp("Installer is located in a Folder that has existing Game Files \n Please make sure the installer is in a fresh folder as per the guide");
|
||||
}
|
||||
|
||||
LogHelper.Info($"Your current game version: { PreCheckHelper.gameVersion }");
|
||||
|
||||
LogHelper.Info("Checking releases for AKI and the Patcher");
|
||||
|
||||
var check = DownloadHelper.ReleaseCheck();
|
||||
|
||||
while (check.Status != System.Threading.Tasks.TaskStatus.RanToCompletion)
|
||||
{
|
||||
}
|
||||
|
||||
LogHelper.Info("Checking if Zips already exist in directory");
|
||||
|
||||
PreCheckHelper.PatcherZipCheck(originalGamePath, targetPath, out string patcherZipPath);
|
||||
PreCheckHelper.AkiZipCheck(targetPath, out string akiZipPath);
|
||||
|
||||
if (patcherZipPath == null && DownloadHelper.patchNeedCheck)
|
||||
{
|
||||
LogHelper.Info("No Patcher zip file present in directory, downloading...");
|
||||
var task = DownloadHelper.DownloadFile(targetPath, DownloadHelper.patcherLink, "/PATCHERZIP.zip");
|
||||
while(task.Status != System.Threading.Tasks.TaskStatus.RanToCompletion)
|
||||
{
|
||||
}
|
||||
LogHelper.Info("Download Complete!");
|
||||
}
|
||||
|
||||
if (akiZipPath == null)
|
||||
{
|
||||
LogHelper.Info("No AKI zip file present in directory, downloading...");
|
||||
var task = DownloadHelper.DownloadFile(targetPath, DownloadHelper.akiLink, "/AKIZIP.zip");
|
||||
while (task.Status != System.Threading.Tasks.TaskStatus.RanToCompletion)
|
||||
{
|
||||
}
|
||||
LogHelper.Info("Download Complete!");
|
||||
}
|
||||
|
||||
PreCheckHelper.PatcherZipCheck(originalGamePath, targetPath, out patcherZipPath);
|
||||
PreCheckHelper.AkiZipCheck(targetPath, out akiZipPath);
|
||||
|
||||
|
||||
|
||||
GameCopy(originalGamePath, targetPath);
|
||||
|
||||
if (DownloadHelper.patchNeedCheck)
|
||||
{
|
||||
PatcherCopy(targetPath, patcherZipPath);
|
||||
PatcherProcess(targetPath);
|
||||
}
|
||||
|
||||
AkiInstall(targetPath, akiZipPath);
|
||||
DeleteZip(patcherZipPath, akiZipPath);
|
||||
}
|
||||
|
||||
static void GameCopy(string originalGamePath, string targetPath)
|
||||
{
|
||||
LogHelper.Info("Copying game files");
|
||||
FileHelper.CopyDirectory(originalGamePath, targetPath, true);
|
||||
}
|
||||
|
||||
static void PatcherCopy(string targetPath, string patcherZipPath)
|
||||
{
|
||||
LogHelper.Info("Extracting patcher");
|
||||
ZipHelper.ZipDecompress(patcherZipPath, targetPath);
|
||||
FileHelper.FindFolder(patcherZipPath, targetPath, out DirectoryInfo dir);
|
||||
FileHelper.CopyDirectory(dir.FullName, targetPath, true);
|
||||
|
||||
if (dir.Exists)
|
||||
{
|
||||
dir.Delete(true);
|
||||
dir.Refresh();
|
||||
if (dir.Exists)
|
||||
{
|
||||
LogHelper.Error("unable to delete patcher folder");
|
||||
LogHelper.Error($"please delete folder called {dir.FullName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PatcherProcess(string targetPath)
|
||||
{
|
||||
LogHelper.Info("Starting patcher");
|
||||
ProcessHelper patcherProcess = new();
|
||||
patcherProcess.StartProcess(Path.Join(targetPath + "/patcher.exe"), targetPath);
|
||||
|
||||
FileHelper.DeleteFiles(Path.Join(targetPath, "/patcher.exe"));
|
||||
}
|
||||
|
||||
static void AkiInstall(string targetPath, string akiZipPath)
|
||||
{
|
||||
ZipHelper.ZipDecompress(akiZipPath, targetPath);
|
||||
LogHelper.Info("Aki has been extracted");
|
||||
}
|
||||
|
||||
static void DeleteZip(string patcherZipPath, string akiZipPath)
|
||||
{
|
||||
FileHelper.DeleteFiles(patcherZipPath, false);
|
||||
FileHelper.DeleteFiles(akiZipPath, false);
|
||||
|
||||
LogHelper.User("Removed Zips, Press enter to close the installer, you can then delete the installer");
|
||||
LogHelper.User("ENJOY SPT-AKI!");
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
static void CloseApp(string text)
|
||||
{
|
||||
LogHelper.Warning(text);
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
85
Aki.Core/SPTInstaller.cs
Normal file
85
Aki.Core/SPTInstaller.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Spectre.Console;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using SPT_AKI_Installer.Aki.Core.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core
|
||||
{
|
||||
//TODO:
|
||||
// locales, language selection
|
||||
|
||||
public class SPTinstaller
|
||||
{
|
||||
InternalData _data;
|
||||
static void Main()
|
||||
{
|
||||
var host = ConfigureHost();
|
||||
|
||||
var tasks = host.Services.GetServices<LiveTableTask>();
|
||||
|
||||
var taskList = new List<LiveTableTask>(tasks);
|
||||
|
||||
AnsiConsole.Write(new FigletText("SPT-AKI INSTALLER").Centered().Color(Color.Yellow));
|
||||
|
||||
host.Services.GetRequiredService<SPTinstaller>()
|
||||
.RunTasksAsync(taskList)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
public SPTinstaller(
|
||||
InternalData data
|
||||
)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public async Task RunTasksAsync(List<LiveTableTask> tasks)
|
||||
{
|
||||
_data.TargetInstallPath = Environment.CurrentDirectory;
|
||||
|
||||
#if DEBUG
|
||||
var path = AnsiConsole.Ask<string>("[purple]DEBUG[/] [blue]::[/] Enter path to install folder: ").Replace("\"", "");
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
CloseApp($"Path invalid: {path}");
|
||||
}
|
||||
|
||||
_data.TargetInstallPath = path;
|
||||
#endif
|
||||
|
||||
await LiveTableTaskRunner.RunAsync(tasks);
|
||||
CloseApp("SPT is Ready to play");
|
||||
}
|
||||
|
||||
private static IHost ConfigureHost()
|
||||
{
|
||||
return Host.CreateDefaultBuilder().ConfigureServices((_, services) =>
|
||||
{
|
||||
services.AddSingleton<InternalData>();
|
||||
services.AddTransient<LiveTableTask, InitializationTask>();
|
||||
services.AddTransient<LiveTableTask, ReleaseCheckTask>();
|
||||
services.AddTransient<LiveTableTask, DownloadTask>();
|
||||
services.AddTransient<LiveTableTask, CopyClientTask>();
|
||||
services.AddTransient<LiveTableTask, SetupClientTask>();
|
||||
services.AddTransient<SPTinstaller>();
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
static void CloseApp(string text)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]{text.EscapeMarkup()}[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("Press [blue]Enter[/] to close ...");
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
30
Aki.Core/Tasks/CopyClientTask.cs
Normal file
30
Aki.Core/Tasks/CopyClientTask.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using SPT_AKI_Installer.Aki.Helper;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
public class CopyClientTask : LiveTableTask
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
public CopyClientTask(InternalData data) : base("Copy Client Files", false)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public override async Task<GenericResult> RunAsync()
|
||||
{
|
||||
SetStatus("Copying", false);
|
||||
|
||||
var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath);
|
||||
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
||||
|
||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, progress);
|
||||
}
|
||||
}
|
||||
}
|
136
Aki.Core/Tasks/DownloadTask.cs
Normal file
136
Aki.Core/Tasks/DownloadTask.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using CG.Web.MegaApiClient;
|
||||
using Newtonsoft.Json;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using SPT_AKI_Installer.Aki.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
public class DownloadTask : LiveTableTask
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
public DownloadTask(InternalData data) : base("Download Files", false)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
private async Task<GenericResult> BuildMirrorList()
|
||||
{
|
||||
var mirrorListInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "mirrors.json"));
|
||||
|
||||
SetStatus("Downloading Mirror List", false);
|
||||
|
||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
var downloadResult = await DownloadHelper.DownloadFile(mirrorListInfo, _data.PatcherMirrorsLink, progress);
|
||||
|
||||
if (!downloadResult.Succeeded)
|
||||
{
|
||||
return downloadResult;
|
||||
}
|
||||
|
||||
var blah = JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(mirrorListInfo.FullName));
|
||||
|
||||
if (blah is List<string> mirrors)
|
||||
{
|
||||
_data.PatcherReleaseMirrors = mirrors;
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
|
||||
return GenericResult.FromError("Failed to deserialize mirrors list");
|
||||
}
|
||||
|
||||
private async Task<GenericResult> DownloadPatcherFromMirrors(IProgress<double> progress)
|
||||
{
|
||||
foreach (string mirror in _data.PatcherReleaseMirrors)
|
||||
{
|
||||
SetStatus($"Downloading Patcher: {mirror}", false);
|
||||
|
||||
// mega is a little weird since they use encryption, but thankfully there is a great library for their api :)
|
||||
if (mirror.StartsWith("https://mega"))
|
||||
{
|
||||
var megaClient = new MegaApiClient();
|
||||
await megaClient.LoginAnonymousAsync();
|
||||
|
||||
// if mega fails to connect, try the next mirror
|
||||
if (!megaClient.IsLoggedIn) continue;
|
||||
|
||||
try
|
||||
{
|
||||
using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror), progress);
|
||||
using var patcherFileStream = _data.PatcherZipInfo.Open(FileMode.Create);
|
||||
{
|
||||
await megaDownloadStream.CopyToAsync(patcherFileStream);
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var result = await DownloadHelper.DownloadFile(_data.PatcherZipInfo, mirror, progress);
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResult.FromError("Failed to download Patcher");
|
||||
}
|
||||
|
||||
public override async Task<GenericResult> RunAsync()
|
||||
{
|
||||
_data.PatcherZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.zip"));
|
||||
_data.AkiZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "sptaki.zip"));
|
||||
|
||||
if (_data.PatchNeeded)
|
||||
{
|
||||
if (_data.PatcherZipInfo.Exists) _data.PatcherZipInfo.Delete();
|
||||
|
||||
var buildResult = await BuildMirrorList();
|
||||
|
||||
if (!buildResult.Succeeded)
|
||||
{
|
||||
return buildResult;
|
||||
}
|
||||
|
||||
Progress = 0;
|
||||
|
||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
var patcherDownloadRresult = await DownloadPatcherFromMirrors(progress);
|
||||
|
||||
if (!patcherDownloadRresult.Succeeded)
|
||||
{
|
||||
return patcherDownloadRresult;
|
||||
}
|
||||
}
|
||||
|
||||
if (_data.AkiZipInfo.Exists) _data.AkiZipInfo.Delete();
|
||||
|
||||
SetStatus("Downloading SPT-AKI", false);
|
||||
|
||||
Progress = 0;
|
||||
|
||||
var akiProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
var releaseDownloadResult = await DownloadHelper.DownloadFile(_data.AkiZipInfo, _data.AkiReleaseDownloadLink, akiProgress);
|
||||
|
||||
if (!releaseDownloadResult.Succeeded)
|
||||
{
|
||||
return releaseDownloadResult;
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
}
|
||||
}
|
46
Aki.Core/Tasks/IntializationTask.cs
Normal file
46
Aki.Core/Tasks/IntializationTask.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using SPT_AKI_Installer.Aki.Helper;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
public class InitializationTask : LiveTableTask
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
public InitializationTask(InternalData data) : base("Startup")
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public override async Task<GenericResult> RunAsync()
|
||||
{
|
||||
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||
|
||||
if (_data.OriginalGamePath == null)
|
||||
{
|
||||
return GenericResult.FromError("EFT IS NOT INSTALLED!");
|
||||
}
|
||||
|
||||
_data.OriginalGameVersion = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
||||
|
||||
if (_data.OriginalGamePath == null)
|
||||
{
|
||||
return GenericResult.FromError("Unable to find EFT OG directory, please make sure EFT is installed. Please also run EFT once");
|
||||
}
|
||||
|
||||
if (_data.OriginalGamePath == _data.TargetInstallPath)
|
||||
{
|
||||
return GenericResult.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
|
||||
{
|
||||
return GenericResult.FromError("Installer is located in a Folder that has existing Game Files. Please make sure the installer is in a fresh folder as per the guide");
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
||||
}
|
||||
}
|
||||
}
|
84
Aki.Core/Tasks/ReleaseCheckTask.cs
Normal file
84
Aki.Core/Tasks/ReleaseCheckTask.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using Gitea.Api;
|
||||
using Gitea.Client;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
public class ReleaseCheckTask : LiveTableTask
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
public ReleaseCheckTask(InternalData data) : base("Release Checks")
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public override async Task<GenericResult> RunAsync()
|
||||
{
|
||||
Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
|
||||
|
||||
var repo = new RepositoryApi(Configuration.Default);
|
||||
|
||||
try
|
||||
{
|
||||
SetStatus("Checking SPT Releases", false);
|
||||
|
||||
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
||||
|
||||
SetStatus("Checking for Patches", false);
|
||||
|
||||
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches");
|
||||
|
||||
var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0];
|
||||
var latestAkiVersion = latestAkiRelease.Name.Replace('(', ' ').Replace(')', ' ').Split(' ')[3];
|
||||
var comparePatchToAki = patchRepoReleases?.Find(x => x.Name.Contains(_data.OriginalGameVersion) && x.Name.Contains(latestAkiVersion));
|
||||
|
||||
_data.PatcherMirrorsLink = comparePatchToAki?.Assets[0].BrowserDownloadUrl;
|
||||
_data.AkiReleaseDownloadLink = latestAkiRelease.Assets[0].BrowserDownloadUrl;
|
||||
|
||||
int IntAkiVersion = int.Parse(latestAkiVersion);
|
||||
int IntGameVersion = int.Parse(_data.OriginalGameVersion);
|
||||
bool patchNeedCheck = false;
|
||||
|
||||
if (IntGameVersion > IntAkiVersion)
|
||||
{
|
||||
patchNeedCheck = true;
|
||||
}
|
||||
|
||||
if (IntGameVersion < IntAkiVersion)
|
||||
{
|
||||
return GenericResult.FromError("Your client is outdated. Please update EFT");
|
||||
|
||||
}
|
||||
|
||||
if (IntGameVersion == IntAkiVersion)
|
||||
{
|
||||
patchNeedCheck = false;
|
||||
}
|
||||
|
||||
if (comparePatchToAki == null && patchNeedCheck)
|
||||
{
|
||||
return GenericResult.FromError("No patcher available for your version");
|
||||
}
|
||||
|
||||
_data.PatchNeeded = patchNeedCheck;
|
||||
|
||||
string status = $"Current Release: {latestAkiVersion}";
|
||||
|
||||
if (_data.PatchNeeded)
|
||||
{
|
||||
status += " - Patch Available";
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess(status);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//request failed
|
||||
return GenericResult.FromError("Request Failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
Aki.Core/Tasks/SetupClientTask.cs
Normal file
88
Aki.Core/Tasks/SetupClientTask.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using SPT_AKI_Installer.Aki.Helper;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
public class SetupClientTask : LiveTableTask
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
public SetupClientTask(InternalData data) : base("Setup Client", false)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public override async Task<GenericResult> RunAsync()
|
||||
{
|
||||
// extract patcher files
|
||||
SetStatus("Extrating Patcher", false);
|
||||
|
||||
var extractPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
var patcherOutputDir = new DirectoryInfo(Path.Join(_data.TargetInstallPath, "patcher"));
|
||||
|
||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, extractPatcherProgress);
|
||||
|
||||
if (!extractPatcherResult.Succeeded)
|
||||
{
|
||||
return extractPatcherResult;
|
||||
}
|
||||
|
||||
// copy patcher files to install directory
|
||||
SetStatus("Copying Patcher", false);
|
||||
|
||||
var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First();
|
||||
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
||||
|
||||
var copyPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, copyPatcherProgress);
|
||||
|
||||
if (!copyPatcherResult.Succeeded)
|
||||
{
|
||||
return copyPatcherResult;
|
||||
}
|
||||
|
||||
// run patcher
|
||||
SetStatus("Running Patcher");
|
||||
StartDrawingIndeterminateProgress();
|
||||
|
||||
var patcherEXE = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.exe"));
|
||||
|
||||
var patchingResult = ProcessHelper.PatchClientFiles(patcherEXE, targetInstallDirInfo);
|
||||
|
||||
if (!patchingResult.Succeeded)
|
||||
{
|
||||
return patchingResult;
|
||||
}
|
||||
|
||||
// extract release files
|
||||
SetStatus("Extracting Release");
|
||||
StartDrawingProgress();
|
||||
|
||||
var extractReleaseProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, extractReleaseProgress);
|
||||
|
||||
if (!extractReleaseResult.Succeeded)
|
||||
{
|
||||
return extractPatcherResult;
|
||||
}
|
||||
|
||||
// cleanup temp files
|
||||
SetStatus("Cleanup");
|
||||
StartDrawingIndeterminateProgress();
|
||||
|
||||
patcherOutputDir.Delete(true);
|
||||
_data.PatcherZipInfo.Delete();
|
||||
_data.AkiZipInfo.Delete();
|
||||
patcherEXE.Delete();
|
||||
|
||||
return GenericResult.FromSuccess("SPT is Setup. Happy Playing!");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,104 +1,41 @@
|
||||
using Newtonsoft.Json;
|
||||
using HttpClientProgress;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Gitea.Api;
|
||||
using Gitea.Client;
|
||||
using Gitea.Model;
|
||||
using Spectre.Console;
|
||||
using HttpClientProgress;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class DownloadHelper
|
||||
{
|
||||
public static bool patchNeedCheck;
|
||||
public static string akiLink;
|
||||
public static string patcherLink;
|
||||
private static HttpClient _httpClient = new HttpClient() { Timeout = TimeSpan.FromHours(1) };
|
||||
|
||||
public static async Task ReleaseCheck()
|
||||
public static async Task<GenericResult> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress)
|
||||
{
|
||||
Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
|
||||
|
||||
var repo = new RepositoryApi(Configuration.Default);
|
||||
|
||||
try
|
||||
{
|
||||
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches");
|
||||
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
||||
outputFile.Refresh();
|
||||
|
||||
var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0];
|
||||
var latestAkiVersion = latestAkiRelease.Name.Replace('(', ' ').Replace(')', ' ').Split(' ')[3];
|
||||
var comparePatchToAki = patchRepoReleases?.Find(x => x.Name.Contains(PreCheckHelper.gameVersion) && x.Name.Contains(latestAkiVersion));
|
||||
|
||||
patcherLink = comparePatchToAki?.Assets[0].BrowserDownloadUrl;
|
||||
akiLink = latestAkiRelease.Assets[0].BrowserDownloadUrl;
|
||||
|
||||
int IntAkiVersion = int.Parse(latestAkiVersion);
|
||||
int IntGameVersion = int.Parse(PreCheckHelper.gameVersion);
|
||||
|
||||
if (IntGameVersion > IntAkiVersion)
|
||||
{
|
||||
patchNeedCheck = true;
|
||||
}
|
||||
|
||||
if (IntGameVersion < IntAkiVersion)
|
||||
{
|
||||
LogHelper.Info("Client is older than current Aki Version, Please update!");
|
||||
LogHelper.Warning("Press enter to close the app!");
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
if (IntGameVersion == IntAkiVersion)
|
||||
{
|
||||
patchNeedCheck = false;
|
||||
}
|
||||
|
||||
if(comparePatchToAki == null && patchNeedCheck)
|
||||
{
|
||||
LogHelper.Warning("There is no current patcher for your client version and its needed");
|
||||
LogHelper.Warning("Please try again later once a new patcher is uploaded.");
|
||||
LogHelper.Warning("Press enter to close the app!");
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//request failed
|
||||
LogHelper.Info("Request Failed");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task DownloadFile(string targetFilePath, string targetLink, string newFileName)
|
||||
{
|
||||
await AnsiConsole.Progress().Columns(
|
||||
new PercentageColumn(),
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new ElapsedTimeColumn(),
|
||||
new SpinnerColumn()
|
||||
).StartAsync(async (ProgressContext context) =>
|
||||
{
|
||||
var task = context.AddTask("Downloading File");
|
||||
|
||||
var client = new HttpClient();
|
||||
var docUrl = targetLink;
|
||||
var filePath = Path.Join(targetFilePath, newFileName);
|
||||
|
||||
// Setup your progress reporter
|
||||
var progress = new Progress<float>((float progress) =>
|
||||
{
|
||||
task.Value = progress;
|
||||
});
|
||||
if (outputFile.Exists) outputFile.Delete();
|
||||
|
||||
// Use the provided extension method
|
||||
using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
await client.DownloadDataAsync(docUrl, file, progress);
|
||||
});
|
||||
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
await _httpClient.DownloadDataAsync(targetLink, file, progress);
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
if (!outputFile.Exists)
|
||||
{
|
||||
return GenericResult.FromError($"Failed to download {outputFile.Name}");
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return GenericResult.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +1,37 @@
|
||||
using System;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class FileHelper
|
||||
{
|
||||
public static void CopyDirectory(string oldDir, string newDir, bool overwrite)
|
||||
public static GenericResult CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, IProgress<double> progress)
|
||||
{
|
||||
int totalFiles = Directory.GetFiles(oldDir, "*.*", SearchOption.AllDirectories).Length;
|
||||
|
||||
AnsiConsole.Progress().Columns(
|
||||
new PercentageColumn(),
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new ElapsedTimeColumn(),
|
||||
new SpinnerColumn()
|
||||
).Start((ProgressContext context) =>
|
||||
try
|
||||
{
|
||||
var task = context.AddTask("Copying Files", true, totalFiles);
|
||||
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
||||
int processedFiles = 0;
|
||||
|
||||
foreach (string dirPath in Directory.GetDirectories(oldDir, "*", SearchOption.AllDirectories))
|
||||
foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories))
|
||||
{
|
||||
Directory.CreateDirectory(dirPath.Replace(oldDir, newDir));
|
||||
Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName));
|
||||
}
|
||||
|
||||
foreach (string newPath in Directory.GetFiles(oldDir, "*.*", SearchOption.AllDirectories))
|
||||
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Copy(newPath, newPath.Replace(oldDir, newDir), overwrite);
|
||||
task.Increment(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true);
|
||||
processedFiles++;
|
||||
|
||||
public static void DeleteFiles(string filePath, bool allFolders = false)
|
||||
{
|
||||
if (File.Exists(filePath) || Directory.Exists(filePath))
|
||||
{
|
||||
if (filePath.Contains('.'))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.Delete(filePath, allFolders);
|
||||
progress.Report((int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
public static string FindFile(string path, string name)
|
||||
{
|
||||
string[] filePaths = Directory.GetFiles(path);
|
||||
foreach (string file in filePaths)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (file.Contains(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return file;
|
||||
}
|
||||
return GenericResult.FromError(ex.Message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string FindFile(string path, string name, string altName)
|
||||
{
|
||||
string[] filePaths = Directory.GetFiles(path);
|
||||
foreach (string file in filePaths)
|
||||
{
|
||||
if (file.Contains(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
file.Contains(altName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool FindFolder(string patchRef, string targetPath, out DirectoryInfo dir)
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(targetPath).GetDirectories();
|
||||
string patchInner = null;
|
||||
foreach (var file in dirInfo)
|
||||
{
|
||||
if (file.FullName.Contains("patcher", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
patchInner = file.FullName;
|
||||
}
|
||||
}
|
||||
var path = new DirectoryInfo(patchInner);
|
||||
if (path.Exists)
|
||||
{
|
||||
dir = path;
|
||||
return true;
|
||||
}
|
||||
dir = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,52 +6,52 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace HttpClientProgress
|
||||
{
|
||||
public static class HttpClientProgressExtensions
|
||||
{
|
||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
using (var download = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
// no progress... no contentLength... very sad
|
||||
if (progress is null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination);
|
||||
return;
|
||||
}
|
||||
// Such progress and contentLength much reporting Wow!
|
||||
var progressWrapper = new Progress<long>(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
}
|
||||
}
|
||||
public static class HttpClientProgressExtensions
|
||||
{
|
||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<double> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
using (var download = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
// no progress... no contentLength... very sad
|
||||
if (progress is null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination);
|
||||
return;
|
||||
}
|
||||
// Such progress and contentLength much reporting Wow!
|
||||
var progressWrapper = new Progress<long>(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||
}
|
||||
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||
}
|
||||
|
||||
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (bufferSize < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
if (source is null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (!source.CanRead)
|
||||
throw new InvalidOperationException($"'{nameof(source)}' is not readable.");
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
if (!destination.CanWrite)
|
||||
throw new InvalidOperationException($"'{nameof(destination)}' is not writable.");
|
||||
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (bufferSize < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
if (source is null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (!source.CanRead)
|
||||
throw new InvalidOperationException($"'{nameof(source)}' is not readable.");
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
if (!destination.CanWrite)
|
||||
throw new InvalidOperationException($"'{nameof(destination)}' is not writable.");
|
||||
|
||||
var buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
var buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class LogHelper
|
||||
{
|
||||
private static void Log(string tag, string message, string foreground, string background = "black")
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[{foreground} on {background}][[{tag}]]: {message.EscapeMarkup()}[/]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a string to console starting with [USER] with
|
||||
/// Green text
|
||||
/// </summary>
|
||||
public static void User(string text)
|
||||
{
|
||||
Log("USER", text, "green");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a string to console starting with [WARNING] with
|
||||
/// Yellow text
|
||||
/// </summary>
|
||||
public static void Warning(string text)
|
||||
{
|
||||
Log("WARNING", text, "yellow");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a string to console starting with [ERROR] with
|
||||
/// Red text
|
||||
/// </summary>
|
||||
public static void Error(string text)
|
||||
{
|
||||
Log("ERROR", text, "red");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a string to console starting with [INFO] with
|
||||
/// Blue text
|
||||
/// </summary>
|
||||
public static void Info(string text)
|
||||
{
|
||||
Log("INFO", text, "blue");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,13 @@
|
||||
using Microsoft.Win32;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class PreCheckHelper
|
||||
{
|
||||
private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
private static string OGGamePath;
|
||||
public static string gameVersion;
|
||||
private static string patchZip;
|
||||
private static string akiZip;
|
||||
|
||||
public static string DetectOriginalGamePath()
|
||||
{
|
||||
@ -23,50 +18,13 @@ namespace SPT_AKI_Installer.Aki.Helper
|
||||
var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false)
|
||||
?.GetValue("UninstallString");
|
||||
var info = (uninstallStringValue is string key) ? new FileInfo(key) : null;
|
||||
OGGamePath = info?.DirectoryName;
|
||||
|
||||
return OGGamePath;
|
||||
return info?.DirectoryName;
|
||||
}
|
||||
|
||||
public static void GameCheck(out string gamePath)
|
||||
public static string DetectOriginalGameVersion(string gamePath)
|
||||
{
|
||||
string Path = DetectOriginalGamePath();
|
||||
|
||||
if (Path == null)
|
||||
{
|
||||
LogHelper.Error("EFT IS NOT INSTALLED!");
|
||||
LogHelper.Error("Press enter to close the app");
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
gamePath = Path;
|
||||
}
|
||||
|
||||
public static void DetectOriginalGameVersion(string gamePath)
|
||||
{
|
||||
gameVersion = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
|
||||
}
|
||||
|
||||
public static void PatcherZipCheck(string gamePath, string targetPath, out string patcherZipPath)
|
||||
{
|
||||
// example patch name - Patcher.12.12.15.17861.to.12.12.15.17349.zip
|
||||
patchZip = FileHelper.FindFile(targetPath, gameVersion, "Patcher");
|
||||
if (patchZip == null)
|
||||
{
|
||||
patchZip = FileHelper.FindFile(targetPath, "PATCHERZIP");
|
||||
}
|
||||
patcherZipPath = patchZip;
|
||||
}
|
||||
|
||||
public static void AkiZipCheck(string targetPath, out string akiZipPath)
|
||||
{
|
||||
// example aki name - RELEASE-SPT-2.3.1-17349.zip
|
||||
akiZip = FileHelper.FindFile(targetPath, "SPT", "RELEASE");
|
||||
if (akiZip == null)
|
||||
{
|
||||
akiZip = FileHelper.FindFile(targetPath, "AKIZIP");
|
||||
}
|
||||
akiZipPath = akiZip;
|
||||
return FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,100 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public class ProcessHelper
|
||||
public enum PatcherExitCode
|
||||
{
|
||||
private Process _process;
|
||||
private string _exeDir;
|
||||
private string _workingDir;
|
||||
private string response;
|
||||
ProgramClosed = 0,
|
||||
Success = 10,
|
||||
EftExeNotFound = 11,
|
||||
NoPatchFolder = 12,
|
||||
MissingFile = 13,
|
||||
MissingDir = 14,
|
||||
PatchFailed = 15
|
||||
}
|
||||
|
||||
public void StartProcess(string exeDir, string workingDir)
|
||||
public static class ProcessHelper
|
||||
{
|
||||
public static GenericResult PatchClientFiles(FileInfo executable, DirectoryInfo workingDir)
|
||||
{
|
||||
_exeDir = exeDir;
|
||||
_workingDir = workingDir;
|
||||
_process = new Process();
|
||||
_process.StartInfo.FileName = exeDir;
|
||||
_process.StartInfo.WorkingDirectory = workingDir;
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.StartInfo.Arguments = "autoclose";
|
||||
_process.Start();
|
||||
|
||||
_process.WaitForExit();
|
||||
ExitCodeCheck(_process.ExitCode);
|
||||
}
|
||||
|
||||
public void ExitCodeCheck(int exitCode)
|
||||
{
|
||||
/*
|
||||
public enum PatcherExitCode
|
||||
if (!executable.Exists || !workingDir.Exists)
|
||||
{
|
||||
ProgramClosed = 0,
|
||||
Success = 10,
|
||||
EftExeNotFound = 11,
|
||||
NoPatchFolder = 12,
|
||||
MissingFile = 13,
|
||||
MissingDir = 14
|
||||
return GenericResult.FromError($"Could not find executable ({executable.Name}) or working directory ({workingDir.Name})");
|
||||
}
|
||||
*/
|
||||
|
||||
switch (exitCode)
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = executable.FullName;
|
||||
process.StartInfo.WorkingDirectory = workingDir.FullName;
|
||||
process.EnableRaisingEvents = true;
|
||||
process.StartInfo.Arguments = "autoclose";
|
||||
process.Start();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
switch ((PatcherExitCode)process.ExitCode)
|
||||
{
|
||||
case 0:
|
||||
LogHelper.Warning("Patcher was closed before completing!");
|
||||
LogHelper.Warning("If you need to start the patcher again, type retry");
|
||||
LogHelper.Warning("If you want to close the installer, close the app.");
|
||||
response = Console.ReadLine();
|
||||
case PatcherExitCode.Success:
|
||||
return GenericResult.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
||||
|
||||
while (!string.Equals(response, "retry", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
LogHelper.Warning("Answer needs to be retry");
|
||||
LogHelper.Warning("Try Again..");
|
||||
response = Console.ReadLine();
|
||||
}
|
||||
if (string.Equals(response, "retry", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
StartProcess(_exeDir, _workingDir);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PatcherExitCode.ProgramClosed:
|
||||
return GenericResult.FromError("Patcher was closed before completing!");
|
||||
|
||||
case 10:
|
||||
LogHelper.Info("Patcher Finished Successfully, extracting Aki");
|
||||
break;
|
||||
case PatcherExitCode.EftExeNotFound:
|
||||
return GenericResult.FromError("EscapeFromTarkov.exe is missing from the install Path");
|
||||
|
||||
case 11:
|
||||
LogHelper.Error("EscapeFromTarkov.exe is missing from the install Path");
|
||||
LogHelper.Warning("Check your game files in their original location are complete!");
|
||||
LogHelper.Warning("Closing the installer in 20 seconds");
|
||||
Thread.Sleep(20000);
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
case PatcherExitCode.NoPatchFolder:
|
||||
return GenericResult.FromError("Patchers Folder called 'Aki_Patches' is missing");
|
||||
|
||||
case 12:
|
||||
LogHelper.Error("Patchers Folder called 'Aki_Patches' missing");
|
||||
LogHelper.Warning("Closing the installer in 20 seconds");
|
||||
Thread.Sleep(20000);
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
case PatcherExitCode.MissingFile:
|
||||
return GenericResult.FromError("EFT files was missing a Vital file to continue");
|
||||
|
||||
case 13:
|
||||
LogHelper.Error("EFT files was missing a Vital file to continue");
|
||||
LogHelper.Warning("please reinstall EFT through the BSG launcher");
|
||||
LogHelper.Warning("Closing the installer in 20 seconds");
|
||||
Thread.Sleep(20000);
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
case PatcherExitCode.PatchFailed:
|
||||
return GenericResult.FromError("A patch failed to apply");
|
||||
|
||||
case 14:
|
||||
LogHelper.Error("Patcher Reported Missing Folder");
|
||||
// check with Waffle what this one is
|
||||
LogHelper.Warning("Closing the installer in 20 seconds");
|
||||
Thread.Sleep(20000);
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
default:
|
||||
return GenericResult.FromError("an unknown error occurred in the patcher");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class SpectreHelper
|
||||
{
|
||||
public static void Figlet(string text, Color color)
|
||||
{
|
||||
AnsiConsole.Write(
|
||||
new FigletText(text)
|
||||
.Centered()
|
||||
.Color(color));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +1,56 @@
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using Spectre.Console;
|
||||
using SPT_AKI_Installer.Aki.Core.Model;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace SPT_AKI_Installer.Aki.Helper
|
||||
{
|
||||
public static class ZipHelper
|
||||
{
|
||||
public static void ZipDecompress(string ArchivePath, string OutputFolderPath)
|
||||
public static GenericResult Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress<double> progress = null)
|
||||
{
|
||||
AnsiConsole.Progress().Columns(
|
||||
new PercentageColumn(),
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new ElapsedTimeColumn(),
|
||||
new SpinnerColumn()
|
||||
).Start((ProgressContext context) =>
|
||||
try
|
||||
{
|
||||
using var archive = ZipArchive.Open(ArchivePath);
|
||||
var entries = archive.Entries.Where(entry => !entry.IsDirectory);
|
||||
var task = context.AddTask("Extracting Files", true, entries.Count());
|
||||
OutputFolderPath.Refresh();
|
||||
|
||||
foreach (var entry in entries)
|
||||
if (!OutputFolderPath.Exists) OutputFolderPath.Create();
|
||||
|
||||
using var archive = ZipArchive.Open(ArchivePath);
|
||||
var totalEntries = archive.Entries.Where(entry => !entry.IsDirectory);
|
||||
int processedEntries = 0;
|
||||
|
||||
foreach (var entry in totalEntries)
|
||||
{
|
||||
entry.WriteToDirectory($"{OutputFolderPath}", new ExtractionOptions()
|
||||
entry.WriteToDirectory(OutputFolderPath.FullName, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
|
||||
task.Increment(1);
|
||||
processedEntries++;
|
||||
|
||||
if (progress != null)
|
||||
{
|
||||
progress.Report(Math.Floor(((double)processedEntries / totalEntries.Count()) * 100));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
OutputFolderPath.Refresh();
|
||||
|
||||
if (!OutputFolderPath.Exists)
|
||||
{
|
||||
return GenericResult.FromError($"Failed to extract files: {ArchivePath.Name}");
|
||||
}
|
||||
|
||||
return GenericResult.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return GenericResult.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<History>True|2022-06-21T18:47:38.7532473Z;True|2022-06-08T18:26:47.7977621+01:00;True|2022-06-06T15:07:18.8067168+01:00;True|2022-06-05T22:55:20.5192697+01:00;True|2022-05-30T13:11:30.6942032+01:00;True|2022-05-30T13:08:08.4269393+01:00;True|2022-05-17T01:06:33.6758525+01:00;True|2022-05-14T01:56:09.8410037+01:00;True|2022-05-14T00:54:24.0683990+01:00;True|2022-05-14T00:53:04.7105427+01:00;True|2022-05-14T00:51:00.6280767+01:00;True|2022-05-14T00:49:19.4630888+01:00;True|2022-05-14T00:47:59.2166156+01:00;</History>
|
||||
<History>True|2022-07-09T17:06:26.5751622Z;True|2022-07-09T12:56:17.1018408-04:00;True|2022-07-09T12:38:17.0878078-04:00;True|2022-07-09T12:18:23.6469737-04:00;True|2022-06-21T14:47:38.7532473-04:00;True|2022-06-08T13:26:47.7977621-04:00;True|2022-06-06T10:07:18.8067168-04:00;True|2022-06-05T17:55:20.5192697-04:00;True|2022-05-30T08:11:30.6942032-04:00;True|2022-05-30T08:08:08.4269393-04:00;True|2022-05-16T20:06:33.6758525-04:00;True|2022-05-13T20:56:09.8410037-04:00;True|2022-05-13T19:54:24.0683990-04:00;True|2022-05-13T19:53:04.7105427-04:00;True|2022-05-13T19:51:00.6280767-04:00;True|2022-05-13T19:49:19.4630888-04:00;True|2022-05-13T19:47:59.2166156-04:00;</History>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -14,8 +14,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FubarCoder.RestSharp.Portable.Core" Version="4.0.8" />
|
||||
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.44.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<_LastSelectedProfileId>C:\Users\craig\source\repos\CWXDEV\CWX-SPTinstaller\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
<_LastSelectedProfileId>Z:\dev_stuff\SPT-AKI-Installer\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user