Example #1
0
        public int GetOutputStreams(string workingDirectory, string command, string param, out List <string> standardOut, out List <string> standardErr)
        {
            Process process = CreateProcess(workingDirectory, command, param);

            var localStandardOut = new List <string>();
            var localStandardErr = new List <string>();

            process.OutputDataReceived += (sender, e) => localStandardOut.Add(e.Data);
            process.ErrorDataReceived  += (sender, e) => localStandardErr.Add(e.Data);

            try
            {
                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                var waiter = new ProcessWaiter(process);
                waiter.WaitForExit();

                standardOut = localStandardOut.Where(s => s != null).ToList();
                standardErr = localStandardErr.Where(s => s != null).ToList();
                return(waiter.ProcessExitCode);
            }
            finally
            {
                process.Dispose();
            }
        }
Example #2
0
        public static void FixPermissions([MarshalAs(UnmanagedType.LPWStr)] string path)
        {
            ProcessWaiter.CheckForProcessesBlockingDir(Path.GetFullPath(path)).ConfigureAwait(false).GetAwaiter().GetResult();

            var batContents = $@"
title Fixing permissions... 
rem Get the localized version of Y/N to pass to takeown to make this work in different locales
for /f ""tokens=1,2 delims=[,]"" %%a in ('""choice <nul 2>nul""') do set ""yes=%%a"" & set ""no=%%b""
echo Press %yes% for yes and %no% for no
set target={ path.Trim(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar, ' ') }
echo off
cls
echo Taking ownership of %target% ...
rem First find is to filter out success messages, second findstr is to filter out empty lines
takeown /F ""%target%"" /R /SKIPSL /D %yes% | find /V ""SUCCESS: The file (or folder):"" | findstr /r /v ""^$""
echo.
echo Fixing access rights ...
icacls ""%target%"" /grant *S-1-1-0:(OI)(CI)F /T /C /L /Q
";
            var batPath     = Path.Combine(Path.GetTempPath(), "hfpatch_fixperms.bat");

            File.WriteAllText(batPath, batContents);

            Process.Start(new ProcessStartInfo("cmd", $"/C \"{batPath}\"")
            {
                WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true
            });
        }
Example #3
0
        internal static bool CheckForRunningProcesses(string[] filters, bool doNotKillSteam, Form parentForm = null)
        {
            var idsToCheck = GetRelatedProcessIds(filters, doNotKillSteam);

            if (idsToCheck.Length > 0)
            {
                if (!ProcessWaiter.ShowDialog(parentForm ?? MessageBoxes.DefaultOwner, idsToCheck.ToArray(), false))
                {
                    return(false);
                }
            }

            return(true);
        }
Example #4
0
        public static async Task <FileInfo> GetTempDownloadFilename()
        {
            var tempPath = Path.Combine(InstallDirectoryHelper.GameDirectory.FullName, "temp\\KKManager_downloads");

retryCreate:
            try
            {
                Directory.CreateDirectory(tempPath);

retry:
                var fileName = Path.Combine(tempPath, Path.GetRandomFileName());
                if (File.Exists(fileName))
                {
                    goto retry;
                }

                return(new FileInfo(fileName));
            }
            catch (IOException ex)
            {
                if (await ProcessWaiter.CheckForProcessesBlockingKoiDir() != true)
                {
                    throw new IOException($"Failed to create file in directory {tempPath} because of an IO issue - {ex.Message}", ex);
                }

                goto retryCreate;
            }
            catch (SecurityException ex)
            {
                if (MessageBox.Show($"Failed to create file in directory {tempPath} because of a security issue - {ex.Message}\n\nDo you want KK Manager to attempt to fix the issue? Click cancel if you want to abort.",
                                    "Could not apply update", MessageBoxButtons.OKCancel, MessageBoxIcon.Error) == DialogResult.OK)
                {
                    var fixPermissions = ProcessTools.FixPermissions(InstallDirectoryHelper.GameDirectory.FullName);
                    if (fixPermissions == null)
                    {
                        throw new IOException($"Failed to create file in directory {tempPath} because of a security issue - {ex.Message}", ex);
                    }
                    fixPermissions.WaitForExit();
                    goto retryCreate;
                }
                throw;
            }
        }
Example #5
0
        /// <summary>
        /// Helper method to copy the last 4 KB of the specified file into a temp file and launch it using the external viewer
        /// </summary>
        /// <param name="basefile">The filename</param>
        private static void LaunchRecentFile(string basefile)
        {
            const int tailSize   = 4096;
            const int bufferSize = 100;

            if (!File.Exists(basefile))
            {
                return;
            }

            try
            {
                string     tempfile = Path.GetTempFileName();
                FileStream outfile  = File.Open(tempfile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
                FileStream fs       = File.Open(basefile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                byte[]     buffer   = new byte[bufferSize];
                if (fs.Length > tailSize)
                {
                    fs.Seek(-tailSize, SeekOrigin.End);
                }

                int count;
                do
                {
                    count = fs.Read(buffer, 0, bufferSize);
                    if (count > 0)
                    {
                        outfile.Write(buffer, 0, count);
                    }
                } while (count > 0);
                outfile.Close();
                fs.Close();

                string xfile = tempfile + ".txt";
                File.Move(tempfile, xfile);
                int           pid = LaunchFile(xfile);
                ProcessWaiter pw  = new ProcessWaiter(pid, xfile);
                Thread        t   = new Thread(new ThreadStart(pw.Execute));
                t.IsBackground = true;
                t.Start();
            }
            catch (IOException) {}
        }
        public int ExecuteCommandBlocking(string command, string parameters, string workingDir, string pathExtension,
                                          Action <string> reportOutputLine)
        {
            if (reportOutputLine != null)
            {
                throw new ArgumentException(nameof(reportOutputLine));
            }
            if (_processId.HasValue)
            {
                throw new InvalidOperationException();
            }

            IDictionary <string, string> envVariables = new Dictionary <string, string>();

            if (!string.IsNullOrEmpty(pathExtension))
            {
                envVariables["PATH"] = Utils.GetExtendedPath(pathExtension);
            }

            _logger.DebugInfo($"Attaching debugger to '{command}' via {DebuggerKind.VsTestFramework} engine");
            if (_printTestOutput)
            {
                _logger.DebugInfo(
                    $"Note that due to restrictions of the VsTest framework, the test executable's output can not be displayed in the test console when debugging tests. Use '{SettingsWrapper.OptionDebuggerKind}' option to overcome this problem.'");
            }

            _processId = _frameworkHandle.LaunchProcessWithDebuggerAttached(command, workingDir, parameters, envVariables);

            ProcessWaiter waiter;

            using (var process = Process.GetProcessById(_processId.Value))
            {
                waiter = new ProcessWaiter(process);
                waiter.WaitForExit();
            }

            _logger.DebugInfo($"Executable {command} returned with exit code {waiter.ProcessExitCode}");
            return(waiter.ProcessExitCode);
        }
        private async void ModUpdateProgress_Shown(object sender, EventArgs e)
        {
            var averageDownloadSpeed = new MovingAverage(20);
            var downloadStartTime    = DateTime.MinValue;

            try
            {
                #region Initialize UI

                progressBar1.Style   = ProgressBarStyle.Marquee;
                progressBar1.Value   = 0;
                progressBar1.Maximum = 1;

                labelPercent.Text = "";

                checkBoxSleep.Enabled = false;

                var random = new Random();
                if (random.Next(0, 10) >= 8)
                {
                    var offset    = random.Next(20, 80);
                    var offsetStr = new string(Enumerable.Repeat(' ', offset).ToArray());
                    labelPercent.Text  = offsetStr + " ( )  ( )\n";
                    labelPercent.Text += offsetStr + "( o . o)";
                }

                olvColumnProgress.Renderer     = new BarRenderer(0, 100);
                olvColumnProgress.AspectGetter = rowObject => (int)Math.Round(((UpdateDownloadItem)rowObject).FinishPercent);
                olvColumnSize.AspectGetter     = rowObject => ((UpdateDownloadItem)rowObject).TotalSize;
                //olvColumnDownloaded.AspectGetter = rowObject => ((UpdateDownloadItem)rowObject).GetDownloadedSize();
                olvColumnStatus.AspectGetter = rowObject =>
                {
                    var item = (UpdateDownloadItem)rowObject;
                    return(item.Exceptions.Count == 0
                        ? item.Status.ToString()
                        : item.Status + " - " + string.Join("; ", item.Exceptions.Select(x => x.Message)));
                };
                olvColumnName.AspectGetter = rowObject => ((UpdateDownloadItem)rowObject).DownloadPath.Name;
                olvColumnNo.AspectGetter   = rowObject => ((UpdateDownloadItem)rowObject).Order;

                fastObjectListView1.PrimarySortColumn   = olvColumnStatus;
                fastObjectListView1.SecondarySortColumn = olvColumnNo;
                fastObjectListView1.Sorting             = SortOrder.Ascending;
                fastObjectListView1.PrimarySortOrder    = SortOrder.Ascending;
                fastObjectListView1.SecondarySortOrder  = SortOrder.Ascending;
                fastObjectListView1.ShowSortIndicators  = true;
                fastObjectListView1.Sort();
                fastObjectListView1.ShowSortIndicator();

                _overallSize = _completedSize = FileSize.Empty;

                #endregion

                SetStatus("Preparing...");
                if (await ProcessWaiter.CheckForProcessesBlockingKoiDir() == false)
                {
                    throw new OperationCanceledException();
                }

                #region Find and select updates

                SetStatus("Searching for mod updates...");
                labelPercent.Text = "Please wait, this might take a couple of minutes.";
                var updateTasks = await UpdateSourceManager.GetUpdates(_cancelToken.Token, _updaters, _autoInstallGuids);

                _cancelToken.Token.ThrowIfCancellationRequested();

                progressBar1.Style = ProgressBarStyle.Blocks;

                if (updateTasks.All(x => x.UpToDate))
                {
                    SetStatus("Everything is up to date!");
                    progressBar1.Value = progressBar1.Maximum;
                    _cancelToken.Cancel();
                    return;
                }

                var isAutoInstall = _autoInstallGuids != null && _autoInstallGuids.Length > 0;
                if (!isAutoInstall)
                {
                    SetStatus($"Found {updateTasks.Count} updates, waiting for user confirmation.");
                    updateTasks = ModUpdateSelectDialog.ShowWindow(this, updateTasks);
                }
                else
                {
                    var skipped = updateTasks.RemoveAll(x => x.UpToDate);
                    SetStatus($"Found {updateTasks.Count} update tasks in silent mode, {skipped} are already up-to-date.", true, true);
                }

                if (updateTasks == null)
                {
                    throw new OperationCanceledException();
                }

                #endregion

                SleepControls.PreventSleepOrShutdown(Handle, "Update is in progress");

                #region Set up update downloader and start downloading

                downloadStartTime = DateTime.Now;

                var downloader    = UpdateDownloadCoordinator.Create(updateTasks);
                var downloadItems = downloader.UpdateItems;

                SetStatus($"{downloadItems.Count(items => items.DownloadSources.Count > 1)} out of {downloadItems.Count} items have more than 1 source", false, true);

                fastObjectListView1.Objects = downloadItems;

                progressBar1.Maximum  = 1000;
                progressBar1.Value    = 0;
                checkBoxSleep.Enabled = true;

                _overallSize = FileSize.SumFileSizes(downloadItems.Select(x => x.TotalSize));

                var lastCompletedSize = FileSize.Empty;
                updateTimer.Tick += (o, args) =>
                {
                    var itemCount = fastObjectListView1.GetItemCount();
                    if (itemCount > 0)
                    {
                        fastObjectListView1.BeginUpdate();
                        fastObjectListView1.RedrawItems(0, itemCount - 1, true);
                        // Needed if user changes sorting column
                        //fastObjectListView1.SecondarySortColumn = olvColumnNo;
                        fastObjectListView1.Sort();
                        fastObjectListView1.EndUpdate();
                    }

                    _completedSize = FileSize.SumFileSizes(downloadItems.Select(x => x.GetDownloadedSize()));

                    var totalPercent = (double)_completedSize.GetKbSize() / (double)_overallSize.GetKbSize() * 100d;
                    if (double.IsNaN(totalPercent))
                    {
                        totalPercent = 0;
                    }

                    // Download speed calc
                    var secondsPassed       = updateTimer.Interval / 1000d;
                    var downloadedSinceLast = FileSize.FromKilobytes((long)((_completedSize - lastCompletedSize).GetKbSize() / secondsPassed));
                    lastCompletedSize = _completedSize;
                    averageDownloadSpeed.Sample(downloadedSinceLast.GetKbSize());
                    var etaSeconds = (_overallSize - _completedSize).GetKbSize() / (double)averageDownloadSpeed.GetAverage();
                    var eta = double.IsNaN(etaSeconds) || etaSeconds <0 || etaSeconds> TimeSpan.MaxValue.TotalSeconds
                        ? "Unknown"
                        : TimeSpan.FromSeconds(etaSeconds).GetReadableTimespan();

                    labelPercent.Text =
                        $"Overall: {totalPercent:F1}% done  ({_completedSize} out of {_overallSize})\r\n" +
                        $"Speed: {downloadedSinceLast}/s  (ETA: {eta})";
                    //$"Speed: {downloadedSinceLast:F1}KB/s";

                    progressBar1.Value = Math.Min((int)Math.Round(totalPercent * 10), progressBar1.Maximum);
                };
                updateTimer.Start();

                SetStatus("Downloading updates...", true, true);

                await downloader.RunUpdate(_cancelToken.Token);

                _cancelToken.Token.ThrowIfCancellationRequested();

                #endregion

                #region Show finish messages

                var failedItems     = downloadItems.Where(x => x.Status == UpdateDownloadStatus.Failed).ToList();
                var unfinishedCount = downloadItems.Count(x => x.Status != UpdateDownloadStatus.Finished);

                var s = $"Successfully updated/removed {downloadItems.Count - unfinishedCount} files from {updateTasks.Count} tasks.";
                if (failedItems.Any())
                {
                    s += $"\nFailed to update {failedItems.Count} files because some sources crashed. Check log for details.";
                }

                SetStatus(s, true, true);

                updateTimer.Stop();
                progressBar1.Value = progressBar1.Maximum;
                labelPercent.Text  = "";

                if (failedItems.Any(x => x.Exceptions.Count > 0))
                {
                    var exceptionMessages = failedItems
                                            .SelectMany(x => x.Exceptions)
                                            .Where(y => !(y is DownloadSourceCrashedException))
                                            // Deal with wrapped exceptions
                                            .Select(y => y.Message.Contains("InnerException") && y.InnerException != null ? y.InnerException.Message : y.Message)
                                            .Distinct();

                    var failDetails = "Reason(s) for failing:\n" + string.Join("\n", exceptionMessages);
                    Console.WriteLine(failDetails);
                    s += " " + failDetails;
                }

                // Sleep before showing a messagebox since the box will block until user clicks ok
                SleepIfNecessary();

                MessageBox.Show(s, "Finished updating", MessageBoxButtons.OK, MessageBoxIcon.Information);

                #endregion
            }
            catch (OutdatedVersionException ex)
            {
                SetStatus("KK Manager needs to be updated to get updates.", true, true);
                ex.ShowKkmanOutdatedMessage();
            }
            catch (OperationCanceledException)
            {
                SetStatus("Update was cancelled by the user.", true, true);
            }
            catch (Exception ex)
            {
                var exceptions = ex is AggregateException aex?aex.Flatten().InnerExceptions : (ICollection <Exception>) new[] { ex };

                if (!exceptions.Any(x => x is OperationCanceledException))
                {
                    SleepIfNecessary();
                }

                SetStatus("Unexpected crash while updating mods, aborting.", true, true);
                SetStatus(string.Join("\n---\n", exceptions), false, true);
                MessageBox.Show("Something unexpected happened and the update could not be completed. Make sure that your internet connection is stable, " +
                                "and that you did not hit your download limits, then try again.\n\nError message (check log for more):\n" + string.Join("\n", exceptions.Select(x => x.Message)),
                                "Update failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                updateTimer.Stop();
                checkBoxSleep.Enabled = false;

                fastObjectListView1.EmptyListMsg = "Nothing was downloaded";

                _cancelToken.Cancel();

                labelPercent.Text = "";
                if (_completedSize != FileSize.Empty)
                {
                    labelPercent.Text += $"Downloaded {_completedSize} out of {_overallSize}";
                    if (downloadStartTime != DateTime.MinValue)
                    {
                        var timeSpent = DateTime.Now - downloadStartTime;
                        labelPercent.Text += $" in {timeSpent.GetReadableTimespan()}";
                    }
                    labelPercent.Text += "\n";
                }
                var averageDlSpeed = averageDownloadSpeed.GetAverage();
                if (averageDlSpeed > 0)
                {
                    labelPercent.Text += $"Average download speed: {new FileSize(averageDlSpeed)}/s";
                }

                progressBar1.Style = ProgressBarStyle.Blocks;
                button1.Enabled    = true;
                button1.Text       = "OK";

                if (_autoInstallGuids != null && _autoInstallGuids.Length > 0)
                {
                    Close();
                }

                SleepControls.AllowSleepOrShutdown(Handle);
            }
        }
Example #8
0
        internal static bool CheckForRunningProcesses(string[] filters, bool doNotKillSteam, Form parentForm = null)
        {
            var myId       = Process.GetCurrentProcess().Id;
            var idsToCheck = new List <int>();

            foreach (var pr in Process.GetProcesses())
            {
                try
                {
                    if (pr.Id == myId)
                    {
                        continue;
                    }

                    if (doNotKillSteam && pr.ProcessName.Equals("steam", StringComparison.InvariantCultureIgnoreCase))
                    {
                        continue;
                    }

                    if (string.IsNullOrEmpty(pr.MainModule.FileName) ||
                        pr.MainModule.FileName.StartsWith(WindowsTools.GetEnvironmentPath(CSIDL.CSIDL_SYSTEM), StringComparison.InvariantCultureIgnoreCase))
                    {
                        continue;
                    }

                    var filenames = pr.Modules.Cast <ProcessModule>()
                                    .Select(x => x.FileName)
                                    .Where(s => !string.IsNullOrEmpty(s))
                                    .Distinct();

                    if (filenames.Any(filename => filters.Any(filter =>
                    {
                        if (string.IsNullOrEmpty(filename))
                        {
                            return(false);
                        }

                        if (!Path.IsPathRooted(filename))
                        {
                            return(false);
                        }

                        return(filename.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase));
                    })))
                    {
                        idsToCheck.Add(pr.Id);
                    }
                }
                catch
                {
                    // Ignore invalid processes
                }
            }

            if (idsToCheck.Count > 0)
            {
                if (!ProcessWaiter.ShowDialog(parentForm ?? MessageBoxes.DefaultOwner, idsToCheck.ToArray(), false))
                {
                    return(false);
                }
            }

            return(true);
        }
        private async void ModUpdateProgress_Shown(object sender, EventArgs e)
        {
            try
            {
                progressBar1.Style   = ProgressBarStyle.Marquee;
                progressBar1.Value   = 0;
                progressBar1.Maximum = 1;

                labelPercent.Text = "";

                var random = new Random();
                if (random.Next(0, 10) >= 9)
                {
                    var offset    = random.Next(0, 136);
                    var offsetStr = new string(Enumerable.Repeat(' ', offset).ToArray());
                    labelPercent.Text  = offsetStr + " ( )  ( )\n";
                    labelPercent.Text += offsetStr + "( o . o)";
                }

                SetStatus("Preparing...");
                if (await ProcessWaiter.CheckForProcessesBlockingKoiDir() == false)
                {
                    throw new OperationCanceledException();
                }

                SetStatus("Searching for mod updates...");
                var updateTasks = await UpdateSourceManager.GetUpdates(_cancelToken.Token, _updaters, _autoInstallGuids);

                _cancelToken.Token.ThrowIfCancellationRequested();

                progressBar1.Style = ProgressBarStyle.Blocks;

                if (updateTasks.All(x => x.UpToDate))
                {
                    SetStatus("Everything is up to date!");
                    progressBar1.Value = progressBar1.Maximum;
                    _cancelToken.Cancel();
                    return;
                }

                if (_autoInstallGuids == null || _autoInstallGuids.Length == 0)
                {
                    SetStatus($"Found {updateTasks.Count} updates, waiting for user confirmation.");
                    updateTasks = ModUpdateSelectDialog.ShowWindow(this, updateTasks);
                }
                else
                {
                    var skipped = updateTasks.RemoveAll(x => x.UpToDate);
                    SetStatus($"Found {updateTasks.Count} update tasks in silent mode, {skipped} are already up-to-date.", true, true);
                }

                if (updateTasks == null)
                {
                    throw new OperationCanceledException();
                }

                _overallSize   = FileSize.SumFileSizes(updateTasks.Select(x => x.TotalUpdateSize));
                _completedSize = FileSize.Empty;

                var allItems = updateTasks.SelectMany(x => x.GetUpdateItems())
                               // Remove unnecessary to avoid potential conflicts if the update is aborted midway and a newer version is added
                               .OrderByDescending(sources => sources.Any(x => x.Item2 is DeleteFileUpdateItem))
                               // Try items with a single source first since they are the most risky
                               .ThenBy(sources => sources.Count())
                               .ThenBy(sources => sources.FirstOrDefault()?.Item2.TargetPath.FullName)
                               .ToList();

                SetStatus($"{allItems.Count(items => items.Count() > 1)} out of {allItems.Count} items have more than 1 source", false, true);

                progressBar1.Maximum = 1000;

                for (var index = 0; index < allItems.Count; index++)
                {
                    _cancelToken.Token.ThrowIfCancellationRequested();

                    var task = allItems[index];

                    SetStatus("Downloading " + task.First().Item2.TargetPath.Name);

                    await UpdateSingleItem(task);
                }

                var s = $"Successfully updated/removed {allItems.Count - _failedItems.Count} files from {updateTasks.Count} tasks.";
                if (_failedItems.Any())
                {
                    s += $"\nFailed to update {_failedItems.Count} files because some sources crashed. Check log for details.";
                }
                SetStatus(s, true, true);
                if (_failedExceptions.Any())
                {
                    var failDetails = "Reason(s) for failing:\n" + string.Join("\n", _failedExceptions.Select(x => x.Message).Distinct());
                    Console.WriteLine(failDetails);
                    s += " " + failDetails;
                }
                MessageBox.Show(s, "Finished updating", MessageBoxButtons.OK, MessageBoxIcon.Information);
                PerformAutoScale();
            }
            catch (OutdatedVersionException ex)
            {
                SetStatus("KK Manager needs to be updated to get updates.", true, true);
                ex.ShowKkmanOutdatedMessage();
            }
            catch (OperationCanceledException)
            {
                SetStatus("Operation was cancelled by the user.", true, true);
            }
            catch (Exception ex)
            {
                var exceptions = ex is AggregateException aex?aex.Flatten().InnerExceptions : (ICollection <Exception>) new[] { ex };

                SetStatus("Crash while updating mods, aborting.", true, true);
                SetStatus(string.Join("\n---\n", exceptions), false, true);
                MessageBox.Show("Something unexpected happened and the update could not be completed. Make sure that your internet connection is stable, " +
                                "and that you did not hit your download limits, then try again.\n\nError message (check log for more):\n" + string.Join("\n", exceptions.Select(x => x.Message)),
                                "Update failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                var wasCancelled = _cancelToken.IsCancellationRequested;
                _cancelToken.Cancel();

                labelPercent.Text = wasCancelled ? "Update was cancelled" : "Update finished";

                if (_completedSize != FileSize.Empty)
                {
                    labelPercent.Text += $"\nSuccessfully downloaded {_completedSize} out of {_overallSize}.";
                }

                progressBar1.Style = ProgressBarStyle.Blocks;
                button1.Enabled    = true;
                button1.Text       = "OK";

                if (_autoInstallGuids != null && _autoInstallGuids.Length > 0)
                {
                    Close();
                }
            }
        }
Example #10
0
        /// <exception cref="IOException">Failed to apply the update.</exception>
        public async Task Update(Progress <double> progressCallback, CancellationToken cancellationToken)
        {
            var downloadTarget = await GetTempDownloadFilename();

            // Need to store the filename because MoveTo changes it to the new filename
            var downloadFilename = downloadTarget.FullName;

            if (RemoteFile != null)
            {
                Console.WriteLine($"Attempting download of {TargetPath.Name} from source {RemoteFile.Source.Origin}");
                await RetryHelper.RetryOnExceptionAsync(async() => await RemoteFile.Download(downloadTarget, progressCallback, cancellationToken), 2, TimeSpan.FromSeconds(10), cancellationToken);

                downloadTarget.Refresh();
                if (!downloadTarget.Exists || downloadTarget.Length != RemoteFile.ItemSize)
                {
                    throw new IOException($"Failed to download the update file {RemoteFile.Name} - the downloaded file doesn't exist or is corrupted");
                }

                Console.WriteLine($"Downloaded {downloadTarget.Length} bytes successfully");
            }

retryDelete:
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(TargetPath.FullName));
                try
                {
                    if (TargetPath.Exists)
                    {
                        Console.WriteLine($"Deleting old file {TargetPath.FullName}");
                        // Prevent issues removing readonly files
                        TargetPath.Attributes = FileAttributes.Normal;
                        TargetPath.Delete();
                        // Make sure the file gets deleted before continuing
                        await Task.Delay(200, cancellationToken);
                    }

                    if (RemoteFile != null)
                    {
                        downloadTarget.MoveTo(TargetPath.FullName);
                    }
                }
                catch (IOException)
                {
                    if (RemoteFile != null)
                    {
                        await Task.Delay(1000, cancellationToken);

                        downloadTarget.Replace(TargetPath.FullName, TargetPath.FullName + ".old", true);
                        await Task.Delay(1000, cancellationToken);

                        File.Delete(TargetPath.FullName + ".old");
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            catch (IOException ex)
            {
                if (await ProcessWaiter.CheckForProcessesBlockingKoiDir() != true)
                {
                    throw RetryHelper.DoNotAttemptToRetry(new IOException($"Failed to apply update {TargetPath.FullName} because of an IO issue - {ex.Message}", ex));
                }

                goto retryDelete;
            }
            catch (SecurityException ex)
            {
                if (MessageBox.Show($"Failed to apply update {TargetPath.FullName} because of a security issue - {ex.Message}\n\nDo you want KK Manager to attempt to fix the issue? Click cancel if you want to abort.", "Could not apply update", MessageBoxButtons.OKCancel, MessageBoxIcon.Error) != DialogResult.OK)
                {
                    throw;
                }

                var fixPermissions = ProcessTools.FixPermissions(InstallDirectoryHelper.GameDirectory.FullName);
                if (fixPermissions == null)
                {
                    throw RetryHelper.DoNotAttemptToRetry(new IOException($"Failed to create file in directory {TargetPath.FullName} because of a security issue - {ex.Message}", ex));
                }
                fixPermissions.WaitForExit();
                goto retryDelete;
            }
            finally
            {
                try { File.Delete(downloadFilename); }
                catch (SystemException ex) { Console.WriteLine(ex); }
            }
        }