diff --git a/assets/locales.json b/assets/locales.json
index 8899bf692..3d43a92b3 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -24841,6 +24841,206 @@
"zh_CN": "如果您在设置中有某些 LDN 口令则可加入此游戏。",
"zh_TW": "你只能加入與 LDN 網路密碼片語 (passphrase) 設定相同的遊戲。"
}
+ },
+ {
+ "ID": "SetupWizardActionBack",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Back",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardActionNext",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Next",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardFirstPageTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Welcome to Ryubing!",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardFirstPageContent",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Ryubing is a fork of the discontinued Nintendo Switch emulator, Ryujinx.\n\nThis setup wizard will guide you through the necessary steps needed for Ryubing to play your Switch games on PC.",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardFirstPageAction",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Start Setup",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardKeysPageTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Key Files",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardKeysPageDescription",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Please select the folder containing your prod/title .keys files:",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SetupWizardKeysPageFolderPopupTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Please select the folder containing your prod/title .keys files",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
}
]
-}
\ No newline at end of file
+}
diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
index 4e0fdf554..8ed8b2a18 100644
--- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
+++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
@@ -219,6 +219,8 @@ namespace Ryujinx.HLE.FileSystem
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
}
+ public bool HasKeySet { get; private set; }
+
public void ReloadKeySet()
{
KeySet ??= KeySet.CreateDefaultKeySet();
@@ -235,6 +237,8 @@ namespace Ryujinx.HLE.FileSystem
LoadSetAtPath(AppDataManager.KeysDirPath);
+ HasKeySet = (prodKeyFile != null && titleKeyFile != null) || prodKeyFile != null;
+
void LoadSetAtPath(string basePath)
{
string localProdKeyFile = Path.Combine(basePath, "prod.keys");
@@ -263,7 +267,12 @@ namespace Ryujinx.HLE.FileSystem
}
}
- ExternalKeyReader.ReadKeyFile(KeySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, null);
+ ExternalKeyReader.ReadKeyFile(
+ KeySet,
+ prodKeyFile,
+ devKeyFile,
+ titleKeyFile,
+ consoleKeyFile);
}
public void ImportTickets(IFileSystem fs)
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index d77e79756..e40f73075 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -32,6 +32,8 @@ namespace Ryujinx.Ava
{
internal static class Program
{
+ public static bool IsFirstStart { get; set; }
+
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
@@ -221,7 +223,6 @@ namespace Ryujinx.Ava
public static void ReloadConfig(bool isRunGameWithCustomConfig = false)
{
-
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
@@ -247,6 +248,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
+ IsFirstStart = true;
}
else
{
diff --git a/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs b/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs
new file mode 100644
index 000000000..5a679884b
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs
@@ -0,0 +1,31 @@
+using Avalonia.Controls.Presenters;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Systems.SetupWizard;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.Systems.SetupWizard
+{
+ public abstract class BaseSetupWizard(ContentPresenter presenter)
+ {
+ ///
+ /// Define the logic and flow of this .
+ ///
+ public abstract ValueTask Start();
+
+ protected ValueTask FirstPage()
+ {
+ SetupWizardPageBuilder builder = new(presenter, isFirstPage: true);
+
+ return builder
+ .WithTitle(LocaleKeys.SetupWizardFirstPageTitle)
+ .WithContent(LocaleKeys.SetupWizardFirstPageContent)
+ .WithActionContent(LocaleKeys.SetupWizardFirstPageAction)
+ .Show();
+ }
+
+ protected SetupWizardPageBuilder NextPage()
+ {
+ return new SetupWizardPageBuilder(presenter);
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/SetupWizard/README.md b/src/Ryujinx/Systems/SetupWizard/README.md
new file mode 100644
index 000000000..158b8a10b
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/README.md
@@ -0,0 +1,3 @@
+# Ryubing Setup Wizard
+
+Directly modified from the code found [here](https://github.com/TKMM-Team/Tkmm/tree/master/src/Tkmm/Wizard).
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs b/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs
new file mode 100644
index 000000000..1f4600e7f
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs
@@ -0,0 +1,62 @@
+using Avalonia.Controls.Presenters;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Systems.SetupWizard;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.Systems.SetupWizard
+{
+ public partial class SetupWizardPage(bool isFirstPage = false) : BaseModel
+ {
+ protected bool? _result;
+ protected readonly CancellationTokenSource _cancellationTokenSource = new();
+
+ public bool IsFirstPage { get; } = isFirstPage;
+
+ [ObservableProperty]
+ private string? _title;
+
+ [ObservableProperty]
+ private object? _content;
+
+ [ObservableProperty]
+ private object? _helpContent;
+
+ [ObservableProperty]
+ private object? _actionContent = LocaleManager.Instance[LocaleKeys.SetupWizardActionNext];
+
+ [RelayCommand]
+ private void MoveBack()
+ {
+ _result = false;
+ _cancellationTokenSource.Cancel();
+ }
+
+ [RelayCommand]
+ private void MoveNext()
+ {
+ _result = true;
+ _cancellationTokenSource.Cancel();
+ }
+
+ public async ValueTask Show(ContentPresenter presenter)
+ {
+ presenter.Content = new SetupWizardPageView
+ {
+ DataContext = this,
+ };
+
+ try {
+ await Task.Delay(-1, _cancellationTokenSource.Token);
+ }
+ catch (TaskCanceledException) {
+ return _result ?? false;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPageBuilder.cs b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageBuilder.cs
new file mode 100644
index 000000000..6e6edffd5
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageBuilder.cs
@@ -0,0 +1,69 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Systems.SetupWizard;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Systems.SetupWizard
+{
+ public class SetupWizardPageBuilder(ContentPresenter presenter, bool isFirstPage = false)
+ {
+ private readonly SetupWizardPage _page = new(isFirstPage);
+
+ public SetupWizardPage Build()
+ {
+ return _page;
+ }
+
+ public SetupWizardPageBuilder WithTitle(LocaleKeys title) => WithTitle(LocaleManager.Instance[title]);
+
+ public SetupWizardPageBuilder WithTitle(string title)
+ {
+ _page.Title = title;
+ return this;
+ }
+
+ public SetupWizardPageBuilder WithContent(LocaleKeys content) => WithContent(LocaleManager.Instance[content]);
+
+ public SetupWizardPageBuilder WithContent(object? content)
+ {
+ if (content is StyledElement { Parent: ContentControl parent }) {
+ parent.Content = null;
+ }
+
+ _page.Content = content;
+ return this;
+ }
+
+ public SetupWizardPageBuilder WithHelpContent(LocaleKeys content) => WithHelpContent(LocaleManager.Instance[content]);
+
+ public SetupWizardPageBuilder WithHelpContent(object? content)
+ {
+ _page.HelpContent = content;
+ return this;
+ }
+
+ public SetupWizardPageBuilder WithContent(object? context = null) where TControl : Control, new()
+ {
+ _page.Content = new TControl {
+ DataContext = context
+ };
+
+ return this;
+ }
+
+ public SetupWizardPageBuilder WithActionContent(LocaleKeys content) => WithActionContent(LocaleManager.Instance[content]);
+
+ public SetupWizardPageBuilder WithActionContent(object? content)
+ {
+ _page.ActionContent = content;
+ return this;
+ }
+
+ public ValueTask Show()
+ {
+ return _page.Show(presenter);
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml
new file mode 100644
index 000000000..dca206897
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml.cs b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml.cs
new file mode 100644
index 000000000..1e0fca7f9
--- /dev/null
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Ryujinx.Ava.Systems.SetupWizard;
+using Ryujinx.Ava.UI.Controls;
+
+namespace Ryujinx.Systems.SetupWizard
+{
+ public partial class SetupWizardPageView : RyujinxControl
+ {
+ public SetupWizardPageView()
+ {
+ InitializeComponent();
+ }
+ }
+}
+
diff --git a/src/Ryujinx/UI/RyujinxApp.axaml.cs b/src/Ryujinx/UI/RyujinxApp.axaml.cs
index 34c2d96ca..eb20c95f2 100644
--- a/src/Ryujinx/UI/RyujinxApp.axaml.cs
+++ b/src/Ryujinx/UI/RyujinxApp.axaml.cs
@@ -15,6 +15,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
+using Ryujinx.UI.SetupWizard;
using System;
using System.Diagnostics;
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml
new file mode 100644
index 000000000..dc243694d
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml.cs b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml.cs
new file mode 100644
index 000000000..7779947c5
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPage.axaml.cs
@@ -0,0 +1,13 @@
+using Ryujinx.Ava.UI.Controls;
+
+namespace Ryujinx.UI.SetupWizard.Pages
+{
+ public partial class SetupKeysPage : RyujinxControl
+ {
+ public SetupKeysPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
+
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs
new file mode 100644
index 000000000..5ae1a0c5c
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs
@@ -0,0 +1,34 @@
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Ryujinx.Ava;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.ViewModels;
+using System.Threading.Tasks;
+
+namespace Ryujinx.UI.SetupWizard.Pages
+{
+ public partial class SetupKeysPageViewModel : BaseModel
+ {
+ [ObservableProperty]
+ public partial string? KeysFolderPath { get; set; }
+
+ [RelayCommand]
+ private static async Task Browse(TextBox tb)
+ {
+ var result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions {
+ Title = LocaleManager.Instance[LocaleKeys.SetupWizardKeysPageFolderPopupTitle],
+ AllowMultiple = false
+ }) switch {
+ [var target] => target.TryGetLocalPath(),
+ _ => null
+ };
+
+ if (result is not null)
+ {
+ tb.Text = result;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs
new file mode 100644
index 000000000..539fe6b61
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs
@@ -0,0 +1,78 @@
+using Avalonia.Controls.Presenters;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Systems.Configuration;
+using Ryujinx.Ava.Systems.SetupWizard;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Common.Logging;
+using Ryujinx.UI.SetupWizard;
+using Ryujinx.UI.SetupWizard.Pages;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Logger = Ryujinx.Common.Logging.Logger;
+
+namespace Ryujinx.Ava.UI.SetupWizard
+{
+ public class RyujinxSetupWizard(ContentPresenter presenter, MainWindowViewModel mwvm, Action onClose) : BaseSetupWizard(presenter)
+ {
+ private bool _configWasModified = false;
+
+ public bool HasFirmware => mwvm.ContentManager.GetCurrentFirmwareVersion() != null;
+
+ public override async ValueTask Start()
+ {
+ RyujinxSetupWizardWindow.IsUsingSetupWizard = true;
+ Start:
+ await FirstPage();
+
+ Keys:
+ if (!mwvm.VirtualFileSystem.HasKeySet)
+ {
+ Retry:
+ SetupKeysPageViewModel kpvm = new();
+ bool result = await NextPage()
+ .WithTitle(LocaleKeys.SetupWizardKeysPageTitle)
+ .WithContent(kpvm)
+ .Show();
+
+ if (!result)
+ goto Start;
+
+ if (!Directory.Exists(kpvm.KeysFolderPath))
+ goto Retry;
+
+ await mwvm.HandleKeysInstallation(kpvm.KeysFolderPath);
+ }
+
+ Firmware:
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ // i know its always false thats the fucking point, its not done
+ if (!HasFirmware && false)
+ {
+ if (!mwvm.VirtualFileSystem.HasKeySet)
+ goto Keys;
+
+ Retry:
+ SetupKeysPageViewModel kpvm = new();
+ bool result = await NextPage()
+ .WithTitle(LocaleKeys.SetupWizardKeysPageTitle)
+ .WithContent(kpvm)
+ .Show();
+
+ if (!result)
+ goto Keys;
+
+ if (!Directory.Exists(kpvm.KeysFolderPath))
+ goto Retry;
+
+ await mwvm.HandleKeysInstallation(kpvm.KeysFolderPath);
+ }
+
+ Return:
+ onClose();
+
+ if (_configWasModified)
+ ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml
new file mode 100644
index 000000000..8f2d59d51
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml.cs
new file mode 100644
index 000000000..0e9d3c3ff
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizardWindow.axaml.cs
@@ -0,0 +1,83 @@
+using Ryujinx.Ava;
+using Ryujinx.Ava.Systems.Configuration;
+using Ryujinx.Ava.UI.SetupWizard;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.Windows;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using System;
+using System.IO;
+
+namespace Ryujinx.UI.SetupWizard
+{
+ public partial class RyujinxSetupWizardWindow : StyleableAppWindow
+ {
+ public static bool IsUsingSetupWizard { get; set; }
+
+ public RyujinxSetupWizardWindow() : base(useCustomTitleBar: true)
+ {
+ InitializeComponent();
+
+ if (Program.PreviewerDetached)
+ {
+ FlushControls.IsVisible = !ConfigurationState.Instance.ShowOldUI;
+ }
+ }
+
+ public static RyujinxSetupWizardWindow CreateWindow(MainWindowViewModel mwvm, out RyujinxSetupWizard setupWizard)
+ {
+ RyujinxSetupWizardWindow window = new();
+ window.DataContext = setupWizard = new RyujinxSetupWizard(window.WizardPresenter, mwvm, () =>
+ {
+ window.Close();
+ IsUsingSetupWizard = false;
+ });
+ window.Height = 600;
+ window.Width = 750;
+ return window;
+ }
+
+ public static bool CanShowSetupWizard =>
+ !File.Exists(Path.Combine(AppDataManager.BaseDirPath, ".DoNotShowSetupWizard"));
+
+ public static bool DisableSetupWizard()
+ {
+ if (!CanShowSetupWizard)
+ return false; //cannot disable; file already doesn't exist, so it's disabled.
+
+ string disableFile = Path.Combine(AppDataManager.BaseDirPath, ".DoNotShowSetupWizard");
+
+ try
+ {
+ File.Create(disableFile, 0).Dispose();
+ File.SetAttributes(disableFile, File.GetAttributes(disableFile) | FileAttributes.Hidden);
+ return true;
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.PrintStack(LogClass.Application, e.Message);
+ return false;
+ }
+ }
+
+ public static bool EnableSetupWizard()
+ {
+ if (CanShowSetupWizard)
+ return false; //cannot enable; file already exists, so it's enabled.
+
+ string disableFile = Path.Combine(AppDataManager.BaseDirPath, ".DoNotShowSetupWizard");
+
+ try
+ {
+ File.Delete(disableFile);
+ return true;
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.PrintStack(LogClass.Application, e.Message);
+ return false;
+ }
+ }
+ }
+}
+
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 651dc901c..97bc619d7 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -45,6 +45,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE.UI;
using Ryujinx.Input.HLE;
+using Ryujinx.UI.SetupWizard;
using SkiaSharp;
using System;
using System.Collections.Generic;
@@ -870,7 +871,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void RefreshGrid()
{
- IObservableList appsList = Applications.ToObservableChangeSet()
+ _ = Applications.ToObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out ReadOnlyObservableCollection apps)
@@ -1013,7 +1014,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- private async Task HandleKeysInstallation(string filename)
+ public async Task HandleKeysInstallation(string filename)
{
try
{
diff --git a/src/Ryujinx/UI/Windows/CompatibilityListWindow.axaml b/src/Ryujinx/UI/Windows/CompatibilityListWindow.axaml
index 56fa06909..26b1d8d51 100644
--- a/src/Ryujinx/UI/Windows/CompatibilityListWindow.axaml
+++ b/src/Ryujinx/UI/Windows/CompatibilityListWindow.axaml
@@ -18,7 +18,6 @@
-
{
- await ShowIntelMacWarningAsync();
+ await Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ await ShowIntelMacWarningAsync();
+
+ if (Program.IsFirstStart && RyujinxSetupWizardWindow.CanShowSetupWizard)
+ {
+ Task windowTask = ShowAsync(RyujinxSetupWizardWindow.CreateWindow(ViewModel, out var wiz), this);
+ _ = wiz.Start();
+ await windowTask;
+ }
+ });
+
if (CommandLineState.FirmwareToInstallPathArg.TryGet(out FilePath fwPath))
{
if (fwPath is { ExistsAsFile: true, Extension: "xci" or "zip" } || fwPath.ExistsAsDirectory)
@@ -150,6 +162,8 @@ namespace Ryujinx.Ava.UI.Windows
else
Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file.");
}
+
+ await CheckLaunchState();
});
}
@@ -399,7 +413,7 @@ namespace Ryujinx.Ava.UI.Windows
}
}
}
- else
+ else if (!RyujinxSetupWizardWindow.IsUsingSetupWizard)
{
ShowKeyErrorOnLoad = false;
@@ -538,8 +552,6 @@ namespace Ryujinx.Ava.UI.Windows
{
LoadApplications();
}
-
- _ = CheckLaunchState();
}
private void SetMainContent(Control content = null)