From 84c9f0975a84814d6525bb1d1e13e4b76eead5f1 Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 10:52:26 -0400
Subject: [PATCH 1/8] DownloadCacheHelper allows direct downloads and checking
 cache

---
 SPTInstaller/Helpers/DownloadCacheHelper.cs | 130 ++++++++++++--------
 1 file changed, 77 insertions(+), 53 deletions(-)

diff --git a/SPTInstaller/Helpers/DownloadCacheHelper.cs b/SPTInstaller/Helpers/DownloadCacheHelper.cs
index 3e51d7f..d6f8e34 100644
--- a/SPTInstaller/Helpers/DownloadCacheHelper.cs
+++ b/SPTInstaller/Helpers/DownloadCacheHelper.cs
@@ -26,23 +26,32 @@ public static class DownloadCacheHelper
         return DirectorySizeHelper.SizeSuffix(cacheSize);
     }
 
-    private static bool CheckCache(FileInfo cacheFile, string expectedHash = null)
+    /// <summary>
+    /// Check if a file in the cache already exists
+    /// </summary>
+    /// <param name="fileName">The name of the file to check for</param>
+    /// <param name="expectedHash">The expected hash of the file in the cache</param>
+    /// <param name="cachedFile">The file found in the cache; null if no file is found</param>
+    /// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
+    public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile) 
+        => CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
+
+    private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
     {
+        fileInCache = cacheFile;
+
         try
         {
             cacheFile.Refresh();
             Directory.CreateDirectory(CachePath);
 
-            if (cacheFile.Exists)
-            {
-                if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash))
-                {
-                    Log.Information($"Using cached file: {cacheFile.Name} - Hash: {expectedHash}");
-                    return true;
-                }
+            if (!cacheFile.Exists || expectedHash == null)
+                return false;
 
-                cacheFile.Delete();
-                cacheFile.Refresh();
+            if (FileHashHelper.CheckHash(cacheFile, expectedHash))
+            {
+                fileInCache = cacheFile;
+                return true;
             }
 
             return false;
@@ -53,8 +62,17 @@ public static class DownloadCacheHelper
         }
     }
 
-    private static async Task<Result> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
+    /// <summary>
+    /// Download a file to the cache folder
+    /// </summary>
+    /// <param name="outputFileName">The file name to save the file as</param>
+    /// <param name="targetLink">The url to download the file from</param>
+    /// <param name="progress">A provider for progress updates</param>
+    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress)
     {
+        var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
+
         try
         {
             // Use the provided extension method
@@ -65,90 +83,96 @@ public static class DownloadCacheHelper
 
             if (!outputFile.Exists)
             {
-                return Result.FromError($"Failed to download {outputFile.Name}");
+                Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
+                return null;
             }
 
-            if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
-            {
-                return Result.FromError("Hash mismatch");
-            }
-
-            return Result.FromSuccess();
+            return outputFile;
         }
         catch (Exception ex)
         {
-            return Result.FromError(ex.Message);
+            Log.Error(ex, "Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
+            return null;
         }
     }
 
-    private static async Task<Result> ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
+    /// <summary>
+    /// Download a file to the cache folder
+    /// </summary>
+    /// <param name="outputFileName">The file name to save the file as</param>
+    /// <param name="downloadStream">The stream the download the file from</param>
+    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, Stream downloadStream)
     {
+        var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
+
         try
         {
-            if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
-
-            using var patcherFileStream = cacheFile.Open(FileMode.Create);
+            using var patcherFileStream = outputFile.Open(FileMode.Create);
             {
                 await downloadStream.CopyToAsync(patcherFileStream);
             }
 
             patcherFileStream.Close();
 
-            if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
+            if (!outputFile.Exists)
             {
-                return Result.FromError("Hash mismatch");
+                Log.Error("Failed to download file from stream: {name}", outputFileName);
+                return null;
             }
 
-            return Result.FromSuccess();
+            return outputFile;
         }
         catch(Exception ex)
         {
-            return Result.FromError(ex.Message);
+            Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
+            return null;
         }
     }
 
-    private static async Task<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
+    /// <summary>
+    /// Get the file from cache or download it
+    /// </summary>
+    /// <param name="fileName">The name of the file to check for in the cache</param>
+    /// <param name="targetLink">The url to download from if the file doesn't exist in the cache</param>
+    /// <param name="progress">A provider for progress updates</param>
+    /// <param name="expectedHash">The expected hash of the cached file</param>
+    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    /// <remarks>Use <see cref="DownloadFileAsync(string, string, IProgress{double})"/> if you don't have an expected cache file hash</remarks>
+    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash)
     {
         try
         {
-            if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
+            if (CheckCache(fileName, expectedHash, out var cacheFile))
+                return cacheFile;
 
-            return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
+            return await DownloadFileAsync(fileName, targetLink, progress);
         }
-        catch(Exception ex)
-        {
-            return Result.FromError(ex.Message);
-        }
-    }
-
-    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash = null)
-    {
-        var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
-
-        try
-        {
-            var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash);
-
-            return result.Succeeded ? cacheFile : null;
-        }
-        catch(Exception ex)
+        catch (Exception ex)
         {
             Log.Error(ex, $"Error while getting file: {fileName}");
             return null;
         }
     }
 
-    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
+    /// <summary>
+    /// Get the file from cache or download it
+    /// </summary>
+    /// <param name="fileName">The name of the file to check for in the cache</param>
+    /// <param name="fileDownloadStream">The stream to download from if the file doesn't exist in the cache</param>
+    /// <param name="expectedHash">The expected hash of the cached file</param>
+    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    /// <remarks>Use <see cref="DownloadFileAsync(string, Stream)"/> if you don't have an expected cache file hash</remarks>
+    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash)
     {
-        var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
-
         try
         {
-            var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash);
+            if (CheckCache(fileName, expectedHash, out var cacheFile))
+                return cacheFile;
 
-            return result.Succeeded ? cacheFile : null;
+            return await DownloadFileAsync(fileName, fileDownloadStream);
         }
-        catch(Exception ex)
+        catch (Exception ex)
         {
             Log.Error(ex, $"Error while getting file: {fileName}");
             return null;

From f0e1abc9bf69256072fa04571ba1223f89d3407a Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 10:53:01 -0400
Subject: [PATCH 2/8] add mirror downloaders to only handle downloading of a
 mirror

---
 SPTInstaller/Interfaces/IMirrorDownloader.cs  |  9 +++++
 .../Models/{ => Mirrors}/DownloadMirror.cs    |  2 +-
 .../Downloaders/HttpMirrorDownloader.cs       | 15 +++++++++
 .../Downloaders/MegaMirrorDownloader.cs       | 33 +++++++++++++++++++
 .../Downloaders/MirrorDownloaderBase.cs       | 13 ++++++++
 5 files changed, 71 insertions(+), 1 deletion(-)
 create mode 100644 SPTInstaller/Interfaces/IMirrorDownloader.cs
 rename SPTInstaller/Models/{ => Mirrors}/DownloadMirror.cs (71%)
 create mode 100644 SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
 create mode 100644 SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
 create mode 100644 SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs

diff --git a/SPTInstaller/Interfaces/IMirrorDownloader.cs b/SPTInstaller/Interfaces/IMirrorDownloader.cs
new file mode 100644
index 0000000..1540620
--- /dev/null
+++ b/SPTInstaller/Interfaces/IMirrorDownloader.cs
@@ -0,0 +1,9 @@
+using SPTInstaller.Models.Mirrors;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Interfaces;
+public interface IMirrorDownloader
+{
+    public DownloadMirror MirrorInfo { get; }
+    public Task<FileInfo?> Download(IProgress<double> progress);
+}
diff --git a/SPTInstaller/Models/DownloadMirror.cs b/SPTInstaller/Models/Mirrors/DownloadMirror.cs
similarity index 71%
rename from SPTInstaller/Models/DownloadMirror.cs
rename to SPTInstaller/Models/Mirrors/DownloadMirror.cs
index 40a83fd..b44a00a 100644
--- a/SPTInstaller/Models/DownloadMirror.cs
+++ b/SPTInstaller/Models/Mirrors/DownloadMirror.cs
@@ -1,4 +1,4 @@
-namespace SPTInstaller.Models;
+namespace SPTInstaller.Models.Mirrors;
 
 public class DownloadMirror
 {
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
new file mode 100644
index 0000000..5a7ca2d
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
@@ -0,0 +1,15 @@
+using SPTInstaller.Helpers;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public class HttpMirrorDownloader : MirrorDownloaderBase
+{
+    public HttpMirrorDownloader(DownloadMirror mirror) : base(mirror)
+    {
+    }
+
+    public override async Task<FileInfo?> Download(IProgress<double> progress)
+    {
+        return await DownloadCacheHelper.DownloadFileAsync("patcher.zip", MirrorInfo.Link, progress);
+    }
+}
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
new file mode 100644
index 0000000..e4d08c1
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
@@ -0,0 +1,33 @@
+using CG.Web.MegaApiClient;
+using SPTInstaller.Helpers;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public class MegaMirrorDownloader : MirrorDownloaderBase
+{
+    public MegaMirrorDownloader(DownloadMirror mirrorInfo) : base(mirrorInfo)
+    {
+    }
+
+    public override async Task<FileInfo?> Download(IProgress<double> progress)
+    {
+        var megaClient = new MegaApiClient();
+        await megaClient.LoginAnonymousAsync();
+
+        // if mega fails to connect, just return
+        if (!megaClient.IsLoggedIn)
+            return null;
+
+        try
+        {
+            using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(MirrorInfo.Link), progress);
+
+            return await DownloadCacheHelper.DownloadFileAsync("patcher.zip", megaDownloadStream);
+        }
+        catch
+        {
+            //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
+            return null;
+        }
+    }
+}
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs b/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs
new file mode 100644
index 0000000..2fb6dcb
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs
@@ -0,0 +1,13 @@
+using SPTInstaller.Interfaces;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public abstract class MirrorDownloaderBase : IMirrorDownloader
+{
+    public DownloadMirror MirrorInfo { get; private set; }
+    public abstract Task<FileInfo?> Download(IProgress<double> progress);
+    public MirrorDownloaderBase(DownloadMirror mirrorInfo)
+    {
+        MirrorInfo = mirrorInfo;
+    }
+}

From 84e67b9ae72f3e17e2108d15b217ee1814af9b01 Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 10:53:49 -0400
Subject: [PATCH 3/8] update download task to handle checking cache and
 downloading if needed

allows for more verbose output during the patcher download process
---
 SPTInstaller/Installer Tasks/DownloadTask.cs | 72 +++++++++-----------
 1 file changed, 34 insertions(+), 38 deletions(-)

diff --git a/SPTInstaller/Installer Tasks/DownloadTask.cs b/SPTInstaller/Installer Tasks/DownloadTask.cs
index 15e651b..011be57 100644
--- a/SPTInstaller/Installer Tasks/DownloadTask.cs	
+++ b/SPTInstaller/Installer Tasks/DownloadTask.cs	
@@ -5,12 +5,18 @@ using SPTInstaller.Models;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using SPTInstaller.Helpers;
+using SPTInstaller.Models.Mirrors;
+using SPTInstaller.Models.Mirrors.Downloaders;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Serilog;
 
 namespace SPTInstaller.Installer_Tasks;
 
 public class DownloadTask : InstallerTaskBase
 {
     private InternalData _data;
+    private List<IMirrorDownloader> _mirrors = new List<IMirrorDownloader>();
+    private string _expectedPatcherHash = "";
 
     public DownloadTask(InternalData data) : base("Download Files")
     {
@@ -19,9 +25,9 @@ public class DownloadTask : InstallerTaskBase
 
     private async Task<IResult> BuildMirrorList()
     {
-        var progress = new Progress<double>((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d));});
+        var progress = new Progress<double>((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d), ProgressStyle.Shown);});
 
-        var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
+        var file = await DownloadCacheHelper.DownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
 
         if (file == null)
         {
@@ -30,52 +36,42 @@ public class DownloadTask : InstallerTaskBase
 
         var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
 
-        if (mirrorsList is List<DownloadMirror> mirrors)
-        {
-            _data.PatcherReleaseMirrors = mirrors;
+        if (mirrorsList == null)
+            return Result.FromError("Failed to deserialize mirrors list");
 
-            return Result.FromSuccess();
+
+        foreach (var mirror in mirrorsList)
+        {
+            switch (mirror.Link)
+            {
+                case string l when l.StartsWith("https://mega"):
+                    _mirrors.Add(new MegaMirrorDownloader(mirror));
+                    break;
+                default:
+                    _mirrors.Add(new HttpMirrorDownloader(mirror));
+                    break;
+            }
         }
 
-        return Result.FromError("Failed to deserialize mirrors list");
+        return Result.FromSuccess("Mirrors list ready");
     }
 
     private async Task<IResult> DownloadPatcherFromMirrors(IProgress<double> progress)
     {
-        foreach (var mirror in _data.PatcherReleaseMirrors)
+        SetStatus("Downloading Patcher", "Checking cache ...", progressStyle: ProgressStyle.Indeterminate);
+
+        if (DownloadCacheHelper.CheckCache("patcher.zip", _expectedPatcherHash, out var cacheFile))
         {
-            SetStatus($"Downloading Patcher", mirror.Link);
+            _data.PatcherZipInfo = cacheFile;
+            Log.Information("Using cached file {fileName} - Hash: {hash}", _data.PatcherZipInfo.Name, _expectedPatcherHash);
+            return Result.FromSuccess();
+        }
 
-            // mega is a little weird since they use encryption, but thankfully there is a great library for their api :)
-            if (mirror.Link.StartsWith("https://mega"))
-            {
-                var megaClient = new MegaApiClient();
-                await megaClient.LoginAnonymousAsync();
+        foreach (var mirror in _mirrors)
+        {
+            SetStatus("Downloading Patcher", mirror.MirrorInfo.Link, progressStyle: ProgressStyle.Indeterminate);
 
-                // if mega fails to connect, try the next mirror
-                if (!megaClient.IsLoggedIn) continue;
-
-                try
-                {
-                    using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress);
-
-                    _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", megaDownloadStream, mirror.Hash);
-
-                    if(_data.PatcherZipInfo == null)
-                    {
-                        continue;
-                    }
-
-                    return Result.FromSuccess();
-                }
-                catch
-                {
-                    //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
-                    continue;
-                }
-            }
-
-            _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", mirror.Link, progress, mirror.Hash);
+            _data.PatcherZipInfo = await mirror.Download(progress);
 
             if (_data.PatcherZipInfo != null)
             {

From 3da29287d7e59d229945677c5620425fc1a97435 Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 10:54:23 -0400
Subject: [PATCH 4/8] remove unneeded data from internal data

---
 SPTInstaller/Models/InternalData.cs | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/SPTInstaller/Models/InternalData.cs b/SPTInstaller/Models/InternalData.cs
index 3f9f423..e81a2b7 100644
--- a/SPTInstaller/Models/InternalData.cs
+++ b/SPTInstaller/Models/InternalData.cs
@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using SPTInstaller.Models.Mirrors;
 
 namespace SPTInstaller.Models;
 
@@ -44,11 +45,6 @@ public class InternalData
     /// </summary>
     public string PatcherMirrorsLink { get; set; }
 
-    /// <summary>
-    /// The release download mirrors for the patcher
-    /// </summary>
-    public List<DownloadMirror> PatcherReleaseMirrors { get; set; } = null;
-
     /// <summary>
     /// Whether or not a patch is needed to downgrade the client files
     /// </summary>

From b6f883ec38160cd99aa53b4086905bbb2092478f Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 10:55:25 -0400
Subject: [PATCH 5/8] update updater

using download file async since a hash isn't needed and also remove new installer from cache when done updating
---
 SPTInstaller/Models/InstallerUpdateInfo.cs | 2 +-
 SPTInstaller/Resources/update.ps1          | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/SPTInstaller/Models/InstallerUpdateInfo.cs b/SPTInstaller/Models/InstallerUpdateInfo.cs
index 2b6e956..bb5769c 100644
--- a/SPTInstaller/Models/InstallerUpdateInfo.cs
+++ b/SPTInstaller/Models/InstallerUpdateInfo.cs
@@ -88,7 +88,7 @@ public class InstallerUpdateInfo : ReactiveObject
 
         var progress = new Progress<double>(x => DownloadProgress = (int)x);
 
-        var file = await DownloadCacheHelper.GetOrDownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
+        var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
 
         if (file == null || !file.Exists)
         {
diff --git a/SPTInstaller/Resources/update.ps1 b/SPTInstaller/Resources/update.ps1
index 7e75bec..5cb9e24 100644
--- a/SPTInstaller/Resources/update.ps1
+++ b/SPTInstaller/Resources/update.ps1
@@ -22,6 +22,9 @@ Start-BitsTransfer -Source $source -Destination $destination -DisplayName "Updat
 
 Remove-Module -Name BitsTransfer
 
+# remove the new installer from the cache folder after it is copied
+Remove-Item -Path $source
+
 Start-Process $destination
 
 Write-Host "Done"
\ No newline at end of file

From 174e21d03f5eebd9eda6c8b57cfd6747fa5bfb59 Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 18:51:20 -0400
Subject: [PATCH 6/8] check hashes in mirror downloaders

---
 .../Models/Mirrors/Downloaders/HttpMirrorDownloader.cs     | 7 ++++++-
 .../Models/Mirrors/Downloaders/MegaMirrorDownloader.cs     | 7 ++++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
index 5a7ca2d..584fe1b 100644
--- a/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
+++ b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
@@ -10,6 +10,11 @@ public class HttpMirrorDownloader : MirrorDownloaderBase
 
     public override async Task<FileInfo?> Download(IProgress<double> progress)
     {
-        return await DownloadCacheHelper.DownloadFileAsync("patcher.zip", MirrorInfo.Link, progress);
+        var file = await DownloadCacheHelper.DownloadFileAsync("patcher.zip", MirrorInfo.Link, progress);
+
+        if (file == null)
+            return null;
+
+        return FileHashHelper.CheckHash(file, MirrorInfo.Hash) ? file : null;
     }
 }
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
index e4d08c1..fb79e35 100644
--- a/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
+++ b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
@@ -22,7 +22,12 @@ public class MegaMirrorDownloader : MirrorDownloaderBase
         {
             using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(MirrorInfo.Link), progress);
 
-            return await DownloadCacheHelper.DownloadFileAsync("patcher.zip", megaDownloadStream);
+            var file = await DownloadCacheHelper.DownloadFileAsync("patcher.zip", megaDownloadStream);
+
+            if (file == null)
+                return null;
+
+            return FileHashHelper.CheckHash(file, MirrorInfo.Hash) ? file : null;
         }
         catch
         {

From 90ceffdfca6d863f80adc399ed3aa6f57010ec29 Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 18:52:33 -0400
Subject: [PATCH 7/8] delete old files before downloaing, fix download task

---
 SPTInstaller/Helpers/DownloadCacheHelper.cs  | 8 ++++++++
 SPTInstaller/Installer Tasks/DownloadTask.cs | 9 ++++-----
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/SPTInstaller/Helpers/DownloadCacheHelper.cs b/SPTInstaller/Helpers/DownloadCacheHelper.cs
index d6f8e34..a11a055 100644
--- a/SPTInstaller/Helpers/DownloadCacheHelper.cs
+++ b/SPTInstaller/Helpers/DownloadCacheHelper.cs
@@ -69,12 +69,16 @@ public static class DownloadCacheHelper
     /// <param name="targetLink">The url to download the file from</param>
     /// <param name="progress">A provider for progress updates</param>
     /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    /// <remarks>If the file exists, it is deleted before downloading</remarks>
     public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress)
     {
         var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
 
         try
         {
+            if (outputFile.Exists)
+                outputFile.Delete();
+
             // Use the provided extension method
             using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
                 await _httpClient.DownloadDataAsync(targetLink, file, progress);
@@ -102,12 +106,16 @@ public static class DownloadCacheHelper
     /// <param name="outputFileName">The file name to save the file as</param>
     /// <param name="downloadStream">The stream the download the file from</param>
     /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
+    /// <remarks>If the file exists, it is deleted before downloading</remarks>
     public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, Stream downloadStream)
     {
         var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
 
         try
         {
+            if (outputFile.Exists)
+                outputFile.Delete();
+
             using var patcherFileStream = outputFile.Open(FileMode.Create);
             {
                 await downloadStream.CopyToAsync(patcherFileStream);
diff --git a/SPTInstaller/Installer Tasks/DownloadTask.cs b/SPTInstaller/Installer Tasks/DownloadTask.cs
index 011be57..374f44e 100644
--- a/SPTInstaller/Installer Tasks/DownloadTask.cs	
+++ b/SPTInstaller/Installer Tasks/DownloadTask.cs	
@@ -1,5 +1,4 @@
-using CG.Web.MegaApiClient;
-using Newtonsoft.Json;
+using Newtonsoft.Json;
 using SPTInstaller.Interfaces;
 using SPTInstaller.Models;
 using System.Collections.Generic;
@@ -7,7 +6,6 @@ using System.Threading.Tasks;
 using SPTInstaller.Helpers;
 using SPTInstaller.Models.Mirrors;
 using SPTInstaller.Models.Mirrors.Downloaders;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Serilog;
 
 namespace SPTInstaller.Installer_Tasks;
@@ -39,9 +37,10 @@ public class DownloadTask : InstallerTaskBase
         if (mirrorsList == null)
             return Result.FromError("Failed to deserialize mirrors list");
 
-
         foreach (var mirror in mirrorsList)
         {
+            _expectedPatcherHash = mirror.Hash;
+
             switch (mirror.Link)
             {
                 case string l when l.StartsWith("https://mega"):
@@ -58,7 +57,7 @@ public class DownloadTask : InstallerTaskBase
 
     private async Task<IResult> DownloadPatcherFromMirrors(IProgress<double> progress)
     {
-        SetStatus("Downloading Patcher", "Checking cache ...", progressStyle: ProgressStyle.Indeterminate);
+        SetStatus("Downloading Patcher", "Verifying cached patcher ...", progressStyle: ProgressStyle.Indeterminate);
 
         if (DownloadCacheHelper.CheckCache("patcher.zip", _expectedPatcherHash, out var cacheFile))
         {

From b444c2470518eac3f93a251f3abe3ae85abe55df Mon Sep 17 00:00:00 2001
From: "waffle.lord" <waffle.lord@hotmail.com>
Date: Thu, 21 Sep 2023 18:52:52 -0400
Subject: [PATCH 8/8] update nuget packages

---
 SPTInstaller/SPTInstaller.csproj | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/SPTInstaller/SPTInstaller.csproj b/SPTInstaller/SPTInstaller.csproj
index b919a07..0951158 100644
--- a/SPTInstaller/SPTInstaller.csproj
+++ b/SPTInstaller/SPTInstaller.csproj
@@ -9,8 +9,8 @@
     <PackageIcon>icon.ico</PackageIcon>
     <ApplicationIcon>Assets\icon.ico</ApplicationIcon>
     <Configurations>Debug;Release;TEST</Configurations>
-    <AssemblyVersion>2.13</AssemblyVersion>
-    <FileVersion>2.13</FileVersion>
+    <AssemblyVersion>2.14</AssemblyVersion>
+    <FileVersion>2.14</FileVersion>
   </PropertyGroup>
 
   <ItemGroup>
@@ -35,12 +35,12 @@
     <PackageReference Include="Avalonia.Diagnostics" Version="11.0.4" />
     <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.4" />
     <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.4" />
-    <PackageReference Include="DialogHost.Avalonia" Version="0.7.6" />
+    <PackageReference Include="DialogHost.Avalonia" Version="0.7.7" />
     <PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8" />
     <PackageReference Include="MegaApiClient" Version="1.10.3" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
     <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
-    <PackageReference Include="SharpCompress" Version="0.33.0" />
+    <PackageReference Include="SharpCompress" Version="0.34.0" />
     <PackageReference Include="System.Reactive" Version="6.0.0" />
   </ItemGroup>