diff --git a/assets/locales.json b/assets/locales.json
index 3d43a92b3..8eaa7b4af 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -25041,6 +25041,156 @@
"zh_CN": "",
"zh_TW": ""
}
+ },
+ {
+ "ID": "SetupWizardFirmwarePageTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Switch Firmware",
+ "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": "SetupWizardFirmwarePageDescription",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Please select the folder or .zip/.xci containing your dumped Nintendo Switch firmware:",
+ "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": "SetupWizardFirmwarePageFolderPopupTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Please select the folder containing your dumped & extracted Switch firmware.",
+ "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": "SetupWizardFirmwarePageFilePopupTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Please select the file containing your dumped Switch firmware.",
+ "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": "SetupWizardFirmwarePageFileBrowse",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Select .zip or .xci",
+ "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": "SetupWizardFirmwarePageFolderBrowse",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Select Extracted Folder",
+ "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": ""
+ }
}
]
}
diff --git a/src/Ryujinx.Common/AsyncWorkQueue.cs b/src/Ryujinx.Common/AsyncWorkQueue.cs
index e3f91c891..5d101cd8a 100644
--- a/src/Ryujinx.Common/AsyncWorkQueue.cs
+++ b/src/Ryujinx.Common/AsyncWorkQueue.cs
@@ -91,7 +91,11 @@ namespace Ryujinx.Common
public void Dispose()
{
- _queue.CompleteAdding();
+ try
+ {
+ _queue.CompleteAdding();
+ } catch (ObjectDisposedException) {}
+
_cts.Cancel();
_workerThread.Join();
diff --git a/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs b/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs
index 90bed2383..afaf97fad 100644
--- a/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs
+++ b/src/Ryujinx/Systems/SetupWizard/BaseSetupWizard.cs
@@ -9,7 +9,7 @@ namespace Ryujinx.Ava.Systems.SetupWizard
///
/// Define the logic and flow of this .
///
- public abstract ValueTask Start();
+ public abstract Task Start();
protected ValueTask FirstPage()
{
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs b/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs
index 032f72830..5681ec90a 100644
--- a/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPage.cs
@@ -21,7 +21,7 @@ namespace Ryujinx.Ava.Systems.SetupWizard
[ObservableProperty]
public partial object? Content { get; set; }
- [ObservableProperty] public partial object? HelpContent { get; set; } = "test";
+ [ObservableProperty] public partial object? HelpContent { get; set; }
[ObservableProperty]
public partial object? ActionContent { get; set; } = LocaleManager.Instance[LocaleKeys.SetupWizardActionNext];
diff --git a/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml
index 88d79ca46..99eb4d071 100644
--- a/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml
+++ b/src/Ryujinx/Systems/SetupWizard/SetupWizardPageView.axaml
@@ -59,8 +59,6 @@
Content="{ext:Locale SetupWizardActionBack}"
Margin="10,0,0,0"
Command="{Binding MoveBackCommand}" />
-
-
+ public static void ShowError(string message, bool waitingExit = false) =>
ShowError(
LocaleManager.Instance[LocaleKeys.DialogErrorTitle],
- $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}"
+ $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}",
+ waitingExit: waitingExit
);
public static void ShowInformation(string title, string text, bool waitingExit = false, Action onClick = null, Action onClose = null) =>
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml
new file mode 100644
index 000000000..95117dafe
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml.cs b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml.cs
new file mode 100644
index 000000000..388f37e4c
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePage.axaml.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Ava.UI.Controls;
+
+namespace Ryujinx.UI.SetupWizard.Pages
+{
+ public partial class SetupFirmwarePage : RyujinxControl
+ {
+ public SetupFirmwarePage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePageViewModel.cs b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePageViewModel.cs
new file mode 100644
index 000000000..9c286e7d5
--- /dev/null
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupFirmwarePageViewModel.cs
@@ -0,0 +1,69 @@
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Gommon;
+using Ryujinx.Ava;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.Utilities;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Ryujinx.UI.SetupWizard.Pages
+{
+ public partial class SetupFirmwarePageViewModel : BaseModel
+ {
+ [ObservableProperty]
+ public partial string FirmwareSourcePath { get; set; }
+
+ [RelayCommand]
+ private static async Task BrowseFile(TextBox tb)
+ {
+ Optional result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
+ {
+ Title = LocaleManager.Instance[LocaleKeys.SetupWizardFirmwarePageFilePopupTitle],
+ FileTypeFilter = new List
+ {
+ new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
+ {
+ Patterns = ["*.xci", "*.zip"],
+ AppleUniformTypeIdentifiers = ["com.ryujinx.xci", "public.zip-archive"],
+ MimeTypes = ["application/x-nx-xci", "application/zip"],
+ },
+ new("XCI")
+ {
+ Patterns = ["*.xci"],
+ AppleUniformTypeIdentifiers = ["com.ryujinx.xci"],
+ MimeTypes = ["application/x-nx-xci"],
+ },
+ new("ZIP")
+ {
+ Patterns = ["*.zip"],
+ AppleUniformTypeIdentifiers = ["public.zip-archive"],
+ MimeTypes = ["application/zip"],
+ }
+ }
+ });
+
+ if (result.TryGet(out IStorageFile firmwareFile))
+ {
+ tb.Text = firmwareFile.TryGetLocalPath();
+ }
+ }
+
+ [RelayCommand]
+ private static async Task BrowseFolder(TextBox tb)
+ {
+ Optional result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
+ {
+ Title = LocaleManager.Instance[LocaleKeys.SetupWizardFirmwarePageFolderPopupTitle]
+ });
+
+ if (result.TryGet(out IStorageFolder firmwareFolder))
+ {
+ tb.Text = firmwareFolder.TryGetLocalPath();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs
index 3cfc74075..234b50d6e 100644
--- a/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs
+++ b/src/Ryujinx/UI/SetupWizard/Pages/SetupKeysPageViewModel.cs
@@ -2,8 +2,10 @@ using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.Utilities;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.SetupWizard.Pages
@@ -11,22 +13,19 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
public partial class SetupKeysPageViewModel : BaseModel
{
[ObservableProperty]
- public partial string? KeysFolderPath { get; set; }
+ 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)
+ Optional result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
- tb.Text = result;
+ Title = LocaleManager.Instance[LocaleKeys.SetupWizardKeysPageFolderPopupTitle]
+ });
+
+ if (result.TryGet(out IStorageFolder keyFolder))
+ {
+ tb.Text = keyFolder.TryGetLocalPath();
}
}
}
diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Helpers.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Helpers.cs
index 3bfb85451..594e8da89 100644
--- a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Helpers.cs
+++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Helpers.cs
@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.UI.SetupWizard
LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, directory);
}
- NotificationHelper.ShowError(message);
+ NotificationHelper.ShowError(message, waitingExit: true);
}
finally
{
@@ -57,7 +57,7 @@ namespace Ryujinx.Ava.UI.SetupWizard
}
catch (MissingKeyException ex)
{
- NotificationHelper.ShowError(ex.ToString());
+ NotificationHelper.ShowError(ex.ToString(), waitingExit: true);
return Result.Failure(NoKeysFoundInFolder.Shared);
}
catch (Exception ex)
diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs
index 2ed1fd5f7..d9eaf04f4 100644
--- a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs
+++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs
@@ -3,29 +3,51 @@ using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Systems.SetupWizard;
+using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.SetupWizard.Pages;
+using Ryujinx.Common.Configuration;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.UI.SetupWizard.Pages;
using System;
using System.IO;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.SetupWizard
{
- public partial class RyujinxSetupWizard(ContentPresenter presenter, MainWindowViewModel mwvm, Action onClose) : BaseSetupWizard(presenter)
+ public partial 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()
+ public override async Task Start()
{
RyujinxSetupWizardWindow.IsUsingSetupWizard = true;
Start:
await FirstPage();
Keys:
+ if (!await SetupKeys())
+ goto Start;
+
+ Firmware:
+ if (!await SetupFirmware())
+ goto Keys;
+
+
+ Return:
+ onClose();
+
+ if (_configWasModified)
+ ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
+ }
+
+ private async ValueTask SetupKeys()
+ {
if (!mwvm.VirtualFileSystem.HasKeySet)
- {
+ {
Retry:
SetupKeysPageViewModel kpvm = new();
bool result = await NextPage()
@@ -34,7 +56,7 @@ namespace Ryujinx.Ava.UI.SetupWizard
.Show();
if (!result)
- goto Start;
+ return false;
if (!Directory.Exists(kpvm.KeysFolderPath))
goto Retry;
@@ -46,35 +68,55 @@ namespace Ryujinx.Ava.UI.SetupWizard
}
}
- 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;
+ return true;
+ }
- if (!Directory.Exists(kpvm.KeysFolderPath))
+ private async ValueTask SetupFirmware()
+ {
+ if (!HasFirmware)
+ {
+ Retry:
+ SetupFirmwarePageViewModel fwvm = new();
+ bool result = await NextPage()
+ .WithTitle(LocaleKeys.SetupWizardFirmwarePageTitle)
+ .WithContent(fwvm)
+ .Show();
+
+ if (!result)
+ return false;
+
+ if (!Directory.Exists(fwvm.FirmwareSourcePath))
goto Retry;
- await mwvm.HandleKeysInstallation(kpvm.KeysFolderPath);
+ try
+ {
+ mwvm.ContentManager.InstallFirmware(fwvm.FirmwareSourcePath);
+ SystemVersion installedFwVer = mwvm.ContentManager.GetCurrentFirmwareVersion();
+ NotificationHelper.ShowInformation(
+ "Firmware installed",
+ $"Installed firmware version {installedFwVer.VersionString}."
+ );
+ mwvm.RefreshFirmwareStatus(installedFwVer);
+
+ // Purge Applet Cache.
+
+ DirectoryInfo miiEditorCacheFolder = new(
+ Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")
+ );
+
+ if (miiEditorCacheFolder.Exists)
+ {
+ miiEditorCacheFolder.Delete(true);
+ }
+ }
+ catch (Exception e)
+ {
+ NotificationHelper.ShowError(e.Message, waitingExit: true);
+ goto Retry;
+ }
}
- Return:
- onClose();
-
- if (_configWasModified)
- ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
+ return true;
}
}
}
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 5d8776f69..6585d3903 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -1377,8 +1377,8 @@ namespace Ryujinx.Ava.UI.ViewModels
Patterns = ["*.zip"],
AppleUniformTypeIdentifiers = ["public.zip-archive"],
MimeTypes = ["application/zip"],
- },
- },
+ }
+ }
});
if (result.HasValue)
@@ -1758,12 +1758,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
- public void RefreshFirmwareStatus()
+ public void RefreshFirmwareStatus(SystemVersion version = null)
{
- SystemVersion version = null;
try
{
- version = ContentManager.GetCurrentFirmwareVersion();
+ version ??= ContentManager.GetCurrentFirmwareVersion();
}
catch (Exception)
{
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
index c461a2ea1..a73b69a74 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
@@ -149,7 +149,7 @@ namespace Ryujinx.Ava.UI.Windows
Task windowTask = ShowAsync(
RyujinxSetupWizardWindow.CreateWindow(ViewModel, out BaseSetupWizard wiz),
this);
- _ = wiz.Start().AsTask();
+ _ = wiz.Start();
await windowTask;
}
});