rework dump

This commit is contained in:
IsWaffle 2022-07-09 00:33:55 -04:00
parent 9c3ab1bad9
commit 0fc99bf5cd
20 changed files with 890 additions and 411 deletions

View File

@ -0,0 +1,21 @@
using Spectre.Console;
using SPT_AKI_Installer.Aki.Core.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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();
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SPT_AKI_Installer.Aki.Core.Interfaces
{
internal interface IProgressableTask
{
public int Progress { get; set; }
}
}

43
Aki.Core/InternalData.cs Normal file
View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
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>
/// 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; }
}
}

View 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);
}
}

View File

@ -0,0 +1,153 @@
using Spectre.Console;
using SPT_AKI_Installer.Aki.Core.Interfaces;
using System;
using System.Text;
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 _continueRendering = 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()
{
_continueRendering = true;
new Task(new Action(() => { RenderIndeterminateProgress(ref _continueRendering); })).Start();
}
public void StartDrawingProgress()
{
_continueRendering = true;
new Task(new Action(() => { RenderProgress(ref _continueRendering); })).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)
{
_continueRendering = 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;
}
}
}

View 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[/]");
}
}
});
}
}
}

View File

@ -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);
}
}
}

145
Aki.Core/SPTInstaller.cs Normal file
View File

@ -0,0 +1,145 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading.Tasks;
using Spectre.Console;
using System.IO;
using SPT_AKI_Installer.Aki.Core.Model;
using SPT_AKI_Installer.Aki.Core.Tasks;
using System.Collections.Generic;
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();
// 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}");
// }
// }
}
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);
}
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<SPTinstaller>();
})
.Build();
}
//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)
{
AnsiConsole.MarkupLine($"[yellow]{text.EscapeMarkup()}[/]");
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Press [blue]Enter[/] to close ...");
Console.ReadKey();
Environment.Exit(0);
}
}
}

View File

@ -0,0 +1,47 @@
using SPT_AKI_Installer.Aki.Core.Model;
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);
try
{
int totalFiles = Directory.GetFiles(_data.OriginalGamePath, "*.*", SearchOption.AllDirectories).Length;
int processedFiles = 0;
foreach (string dirPath in Directory.GetDirectories(_data.OriginalGamePath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(dirPath.Replace(_data.OriginalGamePath, _data.TargetInstallPath));
}
foreach (string newPath in Directory.GetFiles(_data.OriginalGamePath, "*.*", SearchOption.AllDirectories))
{
File.Copy(newPath, newPath.Replace(_data.OriginalGamePath, _data.TargetInstallPath), true);
processedFiles++;
Progress = (int)Math.Floor(((double)processedFiles / totalFiles) * 100);
}
return GenericResult.FromSuccess();
}
catch(Exception ex)
{
return GenericResult.FromError(ex.Message);
}
}
}
}

View File

@ -0,0 +1,138 @@
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;
using CG.Web.MegaApiClient;
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(FileInfo patcherZip, IProgress<double> progress)
{
foreach (string mirror in _data.PatcherReleaseMirrors)
{
SetStatus($"Download 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 = patcherZip.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(patcherZip, mirror, progress);
if(result.Succeeded)
{
return GenericResult.FromSuccess();
}
}
return GenericResult.FromError("Failed to download Patcher");
}
public override async Task<GenericResult> RunAsync()
{
var patcherZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.zip"));
var akiZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "sptaki.zip"));
if (_data.PatchNeeded)
{
if (patcherZipInfo.Exists) patcherZipInfo.Delete();
var buildResult = await BuildMirrorList();
if (!buildResult.Succeeded)
{
return buildResult;
}
SetStatus("Downloading Patcher", false);
Progress = 0;
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
var patcherDownloadRresult = await DownloadPatcherFromMirrors(patcherZipInfo, progress);
if (!patcherDownloadRresult.Succeeded)
{
return patcherDownloadRresult;
}
}
if (akiZipInfo.Exists) 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(akiZipInfo, _data.AkiReleaseDownloadLink, akiProgress);
if (!releaseDownloadResult.Succeeded)
{
return releaseDownloadResult;
}
return GenericResult.FromSuccess();
}
}
}

View File

@ -0,0 +1,47 @@
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 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)
{
GenericResult.FromError("Unable to find EFT OG directory \n please make sure EFT is installed \n please also run EFT once");
}
if (_data.OriginalGamePath == _data.TargetInstallPath)
{
GenericResult.FromError("Installer is located in EFT's original directory \n Please move the installer to a seperate folder as per the guide");
}
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
{
GenericResult.FromError("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");
}
return GenericResult.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
}
}
}

View 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");
}
}
}
}

View File

@ -0,0 +1,29 @@
using SPT_AKI_Installer.Aki.Core.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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()
{
/* TODO:
* patch if needed
* extract release
*/
throw new NotImplementedException();
}
}
}

View File

@ -1,104 +1,41 @@
using Newtonsoft.Json; using System;
using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading;
using Gitea.Api;
using Gitea.Client;
using Gitea.Model;
using Spectre.Console;
using HttpClientProgress; using HttpClientProgress;
using SPT_AKI_Installer.Aki.Core.Model;
namespace SPT_AKI_Installer.Aki.Helper namespace SPT_AKI_Installer.Aki.Helper
{ {
public static class DownloadHelper public static class DownloadHelper
{ {
public static bool patchNeedCheck; private static HttpClient _httpClient = new HttpClient() { Timeout = TimeSpan.FromHours(1) };
public static string akiLink;
public static string patcherLink;
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 try
{ {
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches"); outputFile.Refresh();
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0]; if (outputFile.Exists) outputFile.Delete();
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;
});
// Use the provided extension method // Use the provided extension method
using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
await client.DownloadDataAsync(docUrl, file, progress); 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);
}
} }
} }
} }

View File

@ -8,7 +8,7 @@ namespace HttpClientProgress
{ {
public static class HttpClientProgressExtensions public static class HttpClientProgressExtensions
{ {
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default(CancellationToken)) 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)) using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead))
{ {

View File

@ -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");
}
}
}

View File

@ -1,7 +1,6 @@
using Microsoft.Win32; using Microsoft.Win32;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.IO; using System.IO;
using System;
using System.Diagnostics; using System.Diagnostics;
namespace SPT_AKI_Installer.Aki.Helper namespace SPT_AKI_Installer.Aki.Helper
@ -9,10 +8,6 @@ namespace SPT_AKI_Installer.Aki.Helper
public static class PreCheckHelper public static class PreCheckHelper
{ {
private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov"; 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() public static string DetectOriginalGamePath()
{ {
@ -23,50 +18,38 @@ namespace SPT_AKI_Installer.Aki.Helper
var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false) var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false)
?.GetValue("UninstallString"); ?.GetValue("UninstallString");
var info = (uninstallStringValue is string key) ? new FileInfo(key) : null; var info = (uninstallStringValue is string key) ? new FileInfo(key) : null;
OGGamePath = info?.DirectoryName;
return info?.DirectoryName;
return OGGamePath;
} }
public static void GameCheck(out string gamePath) public static string DetectOriginalGameVersion(string gamePath)
{ {
string Path = DetectOriginalGamePath(); return FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
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) public static string GetPatcherZipPath(string gameVersion, string targetPath)
{
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 // example patch name - Patcher.12.12.15.17861.to.12.12.15.17349.zip
patchZip = FileHelper.FindFile(targetPath, gameVersion, "Patcher"); var patchZip = FileHelper.FindFile(targetPath, gameVersion, "Patcher");
if (patchZip == null) if (patchZip == null)
{ {
patchZip = FileHelper.FindFile(targetPath, "PATCHERZIP"); patchZip = FileHelper.FindFile(targetPath, "PATCHERZIP");
} }
patcherZipPath = patchZip;
return patchZip;
} }
public static void AkiZipCheck(string targetPath, out string akiZipPath) public static string GetAkiZipPath(string targetPath)
{ {
// example aki name - RELEASE-SPT-2.3.1-17349.zip // example aki name - RELEASE-SPT-2.3.1-17349.zip
akiZip = FileHelper.FindFile(targetPath, "SPT", "RELEASE"); var akiZip = FileHelper.FindFile(targetPath, "SPT", "RELEASE");
if (akiZip == null) if (akiZip == null)
{ {
akiZip = FileHelper.FindFile(targetPath, "AKIZIP"); akiZip = FileHelper.FindFile(targetPath, "AKIZIP");
} }
akiZipPath = akiZip;
return akiZip;
} }
} }
} }

View File

@ -1,100 +1,54 @@
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using SPT_AKI_Installer.Aki.Core.Model;
using System;
namespace SPT_AKI_Installer.Aki.Helper namespace SPT_AKI_Installer.Aki.Helper
{ {
public enum PatcherExitCode
{
ProgramClosed = 0,
Success = 10,
EftExeNotFound = 11,
NoPatchFolder = 12,
MissingFile = 13,
MissingDir = 14,
PatchFailed = 15
}
public class ProcessHelper public class ProcessHelper
{ {
private Process _process; public GenericResult PatchClientFiles(string exeDir, string workingDir)
private string _exeDir;
private string _workingDir;
private string response;
public void StartProcess(string exeDir, string workingDir)
{ {
_exeDir = exeDir; var process = new Process();
_workingDir = workingDir; process.StartInfo.FileName = exeDir;
_process = new Process(); process.StartInfo.WorkingDirectory = workingDir;
_process.StartInfo.FileName = exeDir; process.EnableRaisingEvents = true;
_process.StartInfo.WorkingDirectory = workingDir; process.StartInfo.Arguments = "autoclose";
_process.EnableRaisingEvents = true; process.Start();
_process.StartInfo.Arguments = "autoclose";
_process.Start();
_process.WaitForExit(); process.WaitForExit();
ExitCodeCheck(_process.ExitCode);
}
public void ExitCodeCheck(int exitCode) switch ((PatcherExitCode)process.ExitCode)
{
/*
public enum PatcherExitCode
{ {
ProgramClosed = 0, case PatcherExitCode.Success:
Success = 10, return GenericResult.FromSuccess("Patcher Finished Successfully, extracting Aki");
EftExeNotFound = 11,
NoPatchFolder = 12,
MissingFile = 13,
MissingDir = 14
}
*/
switch (exitCode) case PatcherExitCode.ProgramClosed:
{ return GenericResult.FromError("Patcher was closed before completing!");
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();
while (!string.Equals(response, "retry", StringComparison.OrdinalIgnoreCase)) case PatcherExitCode.EftExeNotFound:
{ return GenericResult.FromError("EscapeFromTarkov.exe is missing from the install Path");
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 10: case PatcherExitCode.NoPatchFolder:
LogHelper.Info("Patcher Finished Successfully, extracting Aki"); return GenericResult.FromError("Patchers Folder called 'Aki_Patches' is missing");
break;
case 11: case PatcherExitCode.MissingFile:
LogHelper.Error("EscapeFromTarkov.exe is missing from the install Path"); return GenericResult.FromError("EFT files was missing a Vital file to continue");
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 12: case PatcherExitCode.PatchFailed:
LogHelper.Error("Patchers Folder called 'Aki_Patches' missing"); return GenericResult.FromError("A patch failed to apply");
LogHelper.Warning("Closing the installer in 20 seconds");
Thread.Sleep(20000);
Environment.Exit(0);
break;
case 13: default:
LogHelper.Error("EFT files was missing a Vital file to continue"); return GenericResult.FromError("an unknown error occurred in the patcher");
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 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;
} }
} }
} }

View File

@ -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));
}
}
}

View File

@ -14,8 +14,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FubarCoder.RestSharp.Portable.Core" Version="4.0.8" /> <PackageReference Include="FubarCoder.RestSharp.Portable.Core" Version="4.0.8" />
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" 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="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" /> <PackageReference Include="Spectre.Console" Version="0.44.0" />
</ItemGroup> </ItemGroup>