public async Task LaunchAsync()
        {
            if (State == ApplicationState.Patching)
            {
                App.Current.Logger.Info(nameof(MainWindowViewModel), "Cancelling launch");
                _LaunchCancellationTokenSource?.Cancel();
                return;
            }

            UploadErrorButtonVisible = false;
            IsChangelogVisible       = false;

            _ActivityCount += 1;

            App.Current.Logger.Info(nameof(MainWindowViewModel), "Starting launch procedure");
            try
            {
                _LaunchCancellationTokenSource = new CancellationTokenSource();

                App.Current.Logger.Info(nameof(MainWindowViewModel), "Saving client settings");
                await Task.Run(() =>
                {
                    Properties.Settings.Default.EnglishPatchEnabled  = ArksLayerEnglishPatchEnabled;
                    Properties.Settings.Default.TelepipeProxyEnabled = ArksLayerTelepipeProxyEnabled;
                    Properties.Settings.Default.ModFilesEnabled      = ModFilesEnabled;
                    Properties.Settings.Default.Save();
                });

                while (true)
                {
                    try
                    {
                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        var newPhases = new ObservableCollection <PhaseState>();

                        var pso2DirectoriesPhase      = new PSO2DirectoriesPhase(InstallConfiguration);
                        var pso2DirectoriesPhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PSO2Directories"
                        };
                        newPhases.Add(pso2DirectoriesPhaseState);

                        ModFilesPhase modFilesPhase      = null;
                        PhaseState    modFilesPhaseState = null;
                        if (Properties.Settings.Default.ModFilesEnabled)
                        {
                            modFilesPhase      = new ModFilesPhase(InstallConfiguration);
                            modFilesPhaseState = new PhaseState
                            {
                                TitleKey = "MainWindow_Phase_ModFiles"
                            };
                            newPhases.Add(modFilesPhaseState);
                        }

                        var deleteCensorFilePhase      = new DeleteCensorFilePhase(InstallConfiguration);
                        var deleteCensorFilePhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_DeleteCensorFile"
                        };
                        newPhases.Add(deleteCensorFilePhaseState);

                        var downloadConfigurationState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_DownloadConfiguration"
                        };
                        newPhases.Add(downloadConfigurationState);

                        var patchCacheState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PatchCache"
                        };
                        newPhases.Add(patchCacheState);

                        var comparePhase      = new ComparePhase(InstallConfiguration);
                        var comparePhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_Compare"
                        };
                        newPhases.Add(comparePhaseState);
                        {
                            var compareProgressControl = new ProgressControl();
                            compareProgressControl.SetBinding(ProgressControl.ProgressProperty, new Binding
                            {
                                Source = comparePhase.Progress,
                                Path   = new PropertyPath(nameof(comparePhase.Progress.Progress)),
                                Mode   = BindingMode.OneWay
                            });
                            compareProgressControl.SetBinding(ProgressControl.IsIndeterminateProperty, new Binding
                            {
                                Source = comparePhase.Progress,
                                Path   = new PropertyPath(nameof(comparePhase.Progress.IsIndeterminate)),
                                Mode   = BindingMode.OneWay
                            });
                            comparePhaseState.Child = compareProgressControl;
                        }

                        var verifyFilesPhase      = new VerifyFilesPhase(InstallConfiguration);
                        var verifyFilesPhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_VerifyFiles"
                        };
                        newPhases.Add(verifyFilesPhaseState);
                        {
                            var verifyFilesControl = new ProgressControl();
                            verifyFilesControl.SetBinding(ProgressControl.ProgressProperty, new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.Progress)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControl.SetBinding(ProgressControl.IsIndeterminateProperty, new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.IsIndeterminate)),
                                Mode   = BindingMode.OneWay
                            });
                            var verifyFilesControlMessageBinding = new MultiBinding
                            {
                                Converter = StringFormatValueConverter.Instance
                            };
                            verifyFilesControlMessageBinding.Bindings.Add(new LocaleBindingExtension("MainWindow_Phase_VerifyFiles_Message"));
                            verifyFilesControlMessageBinding.Bindings.Add(new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.CompletedCount)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControlMessageBinding.Bindings.Add(new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.TotalCount)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControl.SetBinding(ProgressControl.MessageProperty, verifyFilesControlMessageBinding);
                            verifyFilesPhaseState.Child = verifyFilesControl;
                        }

                        var pluginInfoState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PluginInfo"
                        };
                        newPhases.Add(pluginInfoState);

                        var pso2hPhase      = new PSO2hPhase(InstallConfiguration, ArksLayerEnglishPatchEnabled || ArksLayerTelepipeProxyEnabled);
                        var pso2hPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerEnglishPatchEnabled || ArksLayerTelepipeProxyEnabled
                                ? "MainWindow_Phase_PSO2h_TitleEnabled"
                                : "MainWindow_Phase_PSO2h_TitleDisabled"
                        };
                        newPhases.Add(pso2hPhaseState);

                        var telepipeProxyPhase      = new TelepipeProxyPhase(InstallConfiguration, ArksLayerTelepipeProxyEnabled);
                        var telepipeProxyPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerTelepipeProxyEnabled
                                ? "MainWindow_Phase_TelepipeProxy_TitleEnabled"
                                : "MainWindow_Phase_TelepipeProxy_TitleDisabled"
                        };
                        newPhases.Add(telepipeProxyPhaseState);

                        var englishPatchPhase      = new EnglishPatchPhase(InstallConfiguration, ArksLayerEnglishPatchEnabled);
                        var englishPatchPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerEnglishPatchEnabled
                                ? "MainWindow_Phase_EnglishPatch_TitleEnabled"
                                : "MainWindow_Phase_EnglishPatch_TitleDisabled"
                        };
                        newPhases.Add(englishPatchPhaseState);

                        LargeAddressAwarePhase largeAddressAwarePhase      = null;
                        PhaseState             largeAddressAwarePhaseState = null;
                        if (Properties.Settings.Default.LargeAddressAwareEnabled)
                        {
                            largeAddressAwarePhase      = new LargeAddressAwarePhase(InstallConfiguration);
                            largeAddressAwarePhaseState = new PhaseState
                            {
                                TitleKey = "MainWindow_Phase_LargeAddressAware"
                            };
                            newPhases.Add(largeAddressAwarePhaseState);
                        }

                        var launchPSO2State = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_LaunchPSO2"
                        };
                        newPhases.Add(launchPSO2State);

                        Phases = newPhases;


                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", $"Running {nameof(PSO2DirectoriesPhase)}");
                        await _AttemptPhase(pso2DirectoriesPhaseState,
                                            () => pso2DirectoriesPhase.RunAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        if (modFilesPhase != null && modFilesPhaseState != null)
                        {
                            App.Current.Logger.Info("Launch", $"Running {nameof(ModFilesPhase)}");
                            await _AttemptPhase(modFilesPhaseState,
                                                () => modFilesPhase.RunAsync(_LaunchCancellationTokenSource.Token));
                        }

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", $"Running {nameof(DeleteCensorFilePhase)}");
                        await _AttemptPhase(deleteCensorFilePhaseState,
                                            () => deleteCensorFilePhase.RunAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", "Fetching download configuration");
                        var downloadConfiguration = await _AttemptPhase(downloadConfigurationState,
                                                                        () => DownloadConfiguration.CreateDefaultAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", "Connecting to patch cache database");
                        var patchCache = await _AttemptPhase(patchCacheState,
                                                             () => PatchCache.CreateAsync(InstallConfiguration));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        // loop this block so files that were updated while a possibly long
                        // verify phase took place are not missed
                        while (true)
                        {
                            comparePhaseState.State     = PhaseControl.State.Queued;
                            verifyFilesPhaseState.State = PhaseControl.State.Queued;

                            _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                            App.Current.Logger.Info("Launch", $"Running {nameof(ComparePhase)}");
                            var toUpdate = await _AttemptPhase(comparePhaseState,
                                                               () => comparePhase.RunAsync(downloadConfiguration, patchCache, _LaunchCancellationTokenSource.Token));

                            if (toUpdate.Length == 0)
                            {
                                verifyFilesPhaseState.State = PhaseControl.State.Success;
                                break;
                            }

                            _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                            App.Current.Logger.Info("Launch", $"Running {nameof(VerifyFilesPhase)}");
                            await _AttemptPhase(verifyFilesPhaseState,
                                                () => verifyFilesPhase.RunAsync(toUpdate, patchCache, _LaunchCancellationTokenSource.Token));
                        }

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", "Fetching plugin info");
                        var pluginInfo = await _AttemptPhase(pluginInfoState,
                                                             () => PluginInfo.FetchAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", $"Running {nameof(PSO2hPhase)}");
                        await _AttemptPhase(pso2hPhaseState,
                                            () => pso2hPhase.RunAsync(pluginInfo, _LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", $"Running {nameof(TelepipeProxyPhase)}");
                        await _AttemptPhase(telepipeProxyPhaseState,
                                            () => telepipeProxyPhase.RunAsync(pluginInfo, _LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Current.Logger.Info("Launch", $"Running {nameof(EnglishPatchPhase)}");
                        await _AttemptPhase(englishPatchPhaseState,
                                            () => englishPatchPhase.RunAsync(patchCache, pluginInfo, _LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        if (largeAddressAwarePhaseState != null && largeAddressAwarePhase != null)
                        {
                            App.Current.Logger.Info("Launch", $"Running {nameof(LargeAddressAwarePhase)}");
                            await _AttemptPhase(largeAddressAwarePhaseState,
                                                () => largeAddressAwarePhase.RunAsync(_LaunchCancellationTokenSource.Token));
                        }

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        // cancellation no longer works after this point

                        App.Current.Logger.Info("Launch", "Starting PSO2");

                        await _AttemptPhase(launchPSO2State, async() =>
                        {
                            var startInfo = new ProcessStartInfo()
                            {
                                FileName        = InstallConfiguration.PSO2Executable,
                                Arguments       = "+0x33aca2b9",
                                UseShellExecute = false
                            };
                            startInfo.EnvironmentVariables["-pso2"] = "+0x01e3f1e9";

                            await Task.Run(() =>
                            {
                                var process = new Process()
                                {
                                    StartInfo = startInfo
                                };
                                process.Start();

                                process.WaitForExit();
                            });
                        });

                        App.Current.Logger.Info("Launch", "PSO2 launch process ended");
                    }
                    catch (OperationCanceledException)
                    {
                        App.Current.Logger.Info("Launch", "Launch cancelled");
                        return;
                    }
                    catch (Exception ex)
                    {
                        App.Current.Logger.Info("Launch", "Error during launch phases", ex);
                        UploadErrorButtonVisible = true;

                        try
                        {
                            await Task.Delay(TimeSpan.FromSeconds(10), _LaunchCancellationTokenSource.Token);
                        }
                        catch (OperationCanceledException) { }

                        continue;
                    }

                    break;
                }
            }
            finally
            {
                _ActivityCount -= 1;
                _LaunchCancellationTokenSource = null;
            }
        }
Exemple #2
0
        public async Task LaunchAsync(bool shouldLaunchPSO2 = true)
        {
            IsLaunchingPSO2 = shouldLaunchPSO2;

            if (State == ApplicationState.Patching)
            {
                App.Logger.Info(nameof(MainWindowViewModel), "Cancelling launch");
                _LaunchCancellationTokenSource?.Cancel();
                return;
            }

            UploadErrorButtonVisible = false;
            IsChangelogVisible       = false;

            _ActivityCount += 1;

            App.Logger.Info(nameof(MainWindowViewModel), "Starting launch procedure");
            try
            {
                _LaunchCancellationTokenSource = new CancellationTokenSource();

                App.Logger.Info(nameof(MainWindowViewModel), "Saving client settings");
                await Task.Run(() =>
                {
                    Properties.Settings.Default.EnglishPatchEnabled  = ArksLayerEnglishPatchEnabled;
                    Properties.Settings.Default.TelepipeProxyEnabled = ArksLayerTelepipeProxyEnabled;
                    Properties.Settings.Default.ModFilesEnabled      = ModFilesEnabled;
                    Properties.Settings.Default.Save();
                });

                bool shouldRetry = true;
                while (shouldRetry)
                {
                    try
                    {
                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        var newPhases = new ObservableCollection <PhaseState>();

                        var pso2DirectoriesPhase      = new PSO2DirectoriesPhase(InstallConfiguration);
                        var pso2DirectoriesPhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PSO2Directories"
                        };
                        newPhases.Add(pso2DirectoriesPhaseState);

                        ModFilesPhase modFilesPhase      = null;
                        PhaseState    modFilesPhaseState = null;
                        if (Properties.Settings.Default.ModFilesEnabled)
                        {
                            modFilesPhase      = new ModFilesPhase(InstallConfiguration);
                            modFilesPhaseState = new PhaseState
                            {
                                TitleKey = "MainWindow_Phase_ModFiles"
                            };
                            newPhases.Add(modFilesPhaseState);
                        }

                        var deleteCensorFilePhase      = new DeleteCensorFilePhase(InstallConfiguration);
                        var deleteCensorFilePhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_DeleteCensorFile"
                        };
                        newPhases.Add(deleteCensorFilePhaseState);

                        var downloadConfigurationState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_DownloadConfiguration"
                        };
                        newPhases.Add(downloadConfigurationState);

                        var patchCacheState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PatchCache"
                        };
                        newPhases.Add(patchCacheState);

                        var comparePhase      = new ComparePhase(InstallConfiguration);
                        var comparePhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_Compare"
                        };
                        newPhases.Add(comparePhaseState);
                        {
                            var compareProgressControl = new ProgressControl();
                            compareProgressControl.SetBinding(ProgressControl.ProgressProperty, new Binding
                            {
                                Source = comparePhase.Progress,
                                Path   = new PropertyPath(nameof(comparePhase.Progress.Progress)),
                                Mode   = BindingMode.OneWay
                            });
                            compareProgressControl.SetBinding(ProgressControl.IsIndeterminateProperty, new Binding
                            {
                                Source = comparePhase.Progress,
                                Path   = new PropertyPath(nameof(comparePhase.Progress.IsIndeterminate)),
                                Mode   = BindingMode.OneWay
                            });
                            comparePhaseState.Child = compareProgressControl;
                        }

                        var verifyFilesPhase      = new VerifyFilesPhase(InstallConfiguration);
                        var verifyFilesPhaseState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_VerifyFiles"
                        };
                        newPhases.Add(verifyFilesPhaseState);
                        {
                            var verifyFilesControl = new ProgressControl();
                            verifyFilesControl.SetBinding(ProgressControl.ProgressProperty, new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.Progress)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControl.SetBinding(ProgressControl.IsIndeterminateProperty, new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.IsIndeterminate)),
                                Mode   = BindingMode.OneWay
                            });
                            var verifyFilesControlMessageBinding = new MultiBinding
                            {
                                Converter = StringFormatValueConverter.Instance
                            };
                            verifyFilesControlMessageBinding.Bindings.Add(new LocaleBindingExtension("MainWindow_Phase_VerifyFiles_Message"));
                            verifyFilesControlMessageBinding.Bindings.Add(new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.CompletedCount)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControlMessageBinding.Bindings.Add(new Binding
                            {
                                Source = verifyFilesPhase.Progress,
                                Path   = new PropertyPath(nameof(verifyFilesPhase.Progress.TotalCount)),
                                Mode   = BindingMode.OneWay
                            });
                            verifyFilesControl.SetBinding(ProgressControl.MessageProperty, verifyFilesControlMessageBinding);
                            verifyFilesPhaseState.Child = verifyFilesControl;
                        }

                        var pluginInfoState = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_PluginInfo"
                        };
                        newPhases.Add(pluginInfoState);

                        var pso2hPhase      = new PSO2hPhase(InstallConfiguration, ArksLayerEnglishPatchEnabled || ArksLayerTelepipeProxyEnabled);
                        var pso2hPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerEnglishPatchEnabled || ArksLayerTelepipeProxyEnabled
                                ? "MainWindow_Phase_PSO2h_TitleEnabled"
                                : "MainWindow_Phase_PSO2h_TitleDisabled"
                        };
                        newPhases.Add(pso2hPhaseState);

                        var telepipeProxyPhase      = new TelepipeProxyPhase(InstallConfiguration, ArksLayerTelepipeProxyEnabled);
                        var telepipeProxyPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerTelepipeProxyEnabled
                                ? "MainWindow_Phase_TelepipeProxy_TitleEnabled"
                                : "MainWindow_Phase_TelepipeProxy_TitleDisabled"
                        };
                        newPhases.Add(telepipeProxyPhaseState);

                        var englishPatchPhase      = new EnglishPatchPhase(InstallConfiguration, ArksLayerEnglishPatchEnabled);
                        var englishPatchPhaseState = new PhaseState
                        {
                            TitleKey = ArksLayerEnglishPatchEnabled
                                ? "MainWindow_Phase_EnglishPatch_TitleEnabled"
                                : "MainWindow_Phase_EnglishPatch_TitleDisabled"
                        };
                        newPhases.Add(englishPatchPhaseState);

                        LargeAddressAwarePhase largeAddressAwarePhase      = null;
                        PhaseState             largeAddressAwarePhaseState = null;
                        if (Properties.Settings.Default.LargeAddressAwareEnabled)
                        {
                            largeAddressAwarePhase      = new LargeAddressAwarePhase(InstallConfiguration);
                            largeAddressAwarePhaseState = new PhaseState
                            {
                                TitleKey = "MainWindow_Phase_LargeAddressAware"
                            };
                            newPhases.Add(largeAddressAwarePhaseState);
                        }

                        var launchPSO2State = new PhaseState
                        {
                            TitleKey = "MainWindow_Phase_LaunchPSO2"
                        };
                        if (IsLaunchingPSO2)
                        {
                            newPhases.Add(launchPSO2State);
                        }

                        Phases = newPhases;

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Logger.Info("Launch", $"Running {nameof(PSO2DirectoriesPhase)}");
                        await _AttemptPhase(pso2DirectoriesPhaseState,
                                            () => pso2DirectoriesPhase.RunAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        if (modFilesPhase != null && modFilesPhaseState != null)
                        {
                            App.Logger.Info("Launch", $"Running {nameof(ModFilesPhase)}");
                            await _AttemptPhase(modFilesPhaseState,
                                                () => modFilesPhase.RunAsync(_LaunchCancellationTokenSource.Token));
                        }

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Logger.Info("Launch", $"Running {nameof(DeleteCensorFilePhase)}");
                        await _AttemptPhase(deleteCensorFilePhaseState,
                                            () => deleteCensorFilePhase.RunAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Logger.Info("Launch", "Fetching download configuration");
                        var downloadConfiguration = await _AttemptPhase(downloadConfigurationState,
                                                                        () => DownloadConfiguration.CreateDefaultAsync(_LaunchCancellationTokenSource.Token));

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        App.Logger.Info("Launch", "Connecting to patch cache database");
                        using (var patchCache = await _AttemptPhase(patchCacheState, () => PatchCache.CreateAsync(InstallConfiguration)))
                        {
                            _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                            // loop this block so files that were updated while a possibly long
                            // verify phase took place are not missed
                            while (true)
                            {
                                comparePhaseState.State     = PhaseControl.State.Queued;
                                verifyFilesPhaseState.State = PhaseControl.State.Queued;

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                                App.Logger.Info("Launch", $"Running {nameof(ComparePhase)}");
                                var toUpdate = await _AttemptPhase(comparePhaseState,
                                                                   () => comparePhase.RunAsync(downloadConfiguration, patchCache, _LaunchCancellationTokenSource.Token));

                                if (toUpdate.Length == 0)
                                {
                                    verifyFilesPhaseState.State = PhaseControl.State.Success;
                                    break;
                                }

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                                App.Logger.Info("Launch", $"Running {nameof(VerifyFilesPhase)}");
                                await _AttemptPhase(verifyFilesPhaseState,
                                                    () => verifyFilesPhase.RunAsync(toUpdate, patchCache, _LaunchCancellationTokenSource.Token));
                            }

                            _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                            try
                            {
                                App.Logger.Info("Launch", "Fetching plugin info");
                                var pluginInfo = await _AttemptPhase(pluginInfoState,
                                                                     () => PluginInfo.FetchAsync(_LaunchCancellationTokenSource.Token));

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                                App.Logger.Info("Launch", $"Running {nameof(PSO2hPhase)}");
                                await _AttemptPhase(pso2hPhaseState,
                                                    () => pso2hPhase.RunAsync(pluginInfo, _LaunchCancellationTokenSource.Token));

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                                App.Logger.Info("Launch", $"Running {nameof(TelepipeProxyPhase)}");
                                await _AttemptPhase(telepipeProxyPhaseState,
                                                    () => telepipeProxyPhase.RunAsync(pluginInfo, _LaunchCancellationTokenSource.Token));

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();

                                App.Logger.Info("Launch", $"Running {nameof(EnglishPatchPhase)}");
                                await _AttemptPhase(englishPatchPhaseState,
                                                    () => englishPatchPhase.RunAsync(patchCache, pluginInfo, _LaunchCancellationTokenSource.Token));

                                _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();
                            }
                            catch (OperationCanceledException)
                            {
                                throw;
                            }
                            catch (Exception ex)
                            {
                                App.Logger.Error("Launch", "Error in Arks-Layer tasks", ex);

                                // only bother the user about this stuff if anything related to arks-layer is currently enabled
                                // (currently this wont attempt to clean up anything arks-layer related so if someone was to decide
                                // they no longer want to use the one of the plugins here, the disabling of them would not work)
                                if (ArksLayerEnglishPatchEnabled || ArksLayerTelepipeProxyEnabled)
                                {
                                    var skipArksLayerResult = MessageBox.Show(
                                        LocaleManager.Instance["MainWindow_ArksLayerErrorMessage"],
                                        LocaleManager.Instance["PSRTAstra"],
                                        MessageBoxButton.YesNo,
                                        MessageBoxImage.Exclamation);

                                    if (skipArksLayerResult == MessageBoxResult.No)
                                    {
                                        shouldRetry = false;
                                        throw;
                                    }
                                }
                            }

                            if (largeAddressAwarePhaseState != null && largeAddressAwarePhase != null)
                            {
                                App.Logger.Info("Launch", $"Running {nameof(LargeAddressAwarePhase)}");
                                await _AttemptPhase(largeAddressAwarePhaseState,
                                                    () => largeAddressAwarePhase.RunAsync(_LaunchCancellationTokenSource.Token));
                            }
                        }

                        _LaunchCancellationTokenSource.Token.ThrowIfCancellationRequested();
                        if (!IsLaunchingPSO2)
                        {
                            break;
                        }

                        // cancellation no longer works after this point

                        App.Logger.Info("Launch", "Starting PSO2");

                        await _AttemptPhase(launchPSO2State, async() =>
                        {
                            var startInfo = new ProcessStartInfo()
                            {
                                FileName        = InstallConfiguration.PSO2Executable,
                                Arguments       = "+0x33aca2b9",
                                UseShellExecute = false
                            };
                            startInfo.EnvironmentVariables["-pso2"] = "+0x01e3f1e9";

                            await Task.Run(() =>
                            {
                                var process = new Process()
                                {
                                    StartInfo = startInfo
                                };
                                process.Start();

                                process.WaitForExit();
                            });
                        });

                        App.Logger.Info("Launch", "PSO2 launch process ended");

                        if (Properties.Settings.Default.CloseOnLaunchEnabled)
                        {
                            App.Current.Shutdown();
                        }
                    }
                    // only properly cancel the launch if it was canceled by us
                    catch (OperationCanceledException ex) when(_LaunchCancellationTokenSource.IsCancellationRequested)
                    {
                        App.Logger.Info("Launch", "Launch cancelled", ex);
                    }
                    catch (Exception ex)
                    {
                        App.Logger.Error("Launch", "Error during launch phases", ex);
                        UploadErrorButtonVisible = true;

                        if (shouldRetry)
                        {
                            try
                            {
                                await Task.Delay(TimeSpan.FromSeconds(10), _LaunchCancellationTokenSource.Token);
                            }
                            catch (OperationCanceledException)
                            {
                                App.Logger.Info("Launch", "Launch was canceled during the auto-restart period");
                            }
                        }

                        continue;
                    }

                    break;
                }
            }
            finally
            {
                _ActivityCount -= 1;
                _LaunchCancellationTokenSource = null;
            }
        }