firmware stage

This commit is contained in:
GreemDev 2025-11-21 03:36:10 -06:00
parent bbd11d19d6
commit 94cb992083
14 changed files with 355 additions and 54 deletions

View file

@ -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": ""
}
}
]
}

View file

@ -91,7 +91,11 @@ namespace Ryujinx.Common
public void Dispose()
{
_queue.CompleteAdding();
try
{
_queue.CompleteAdding();
} catch (ObjectDisposedException) {}
_cts.Cancel();
_workerThread.Join();

View file

@ -9,7 +9,7 @@ namespace Ryujinx.Ava.Systems.SetupWizard
/// <summary>
/// Define the logic and flow of this <see cref="BaseSetupWizard"/>.
/// </summary>
public abstract ValueTask Start();
public abstract Task Start();
protected ValueTask<bool> FirstPage()
{

View file

@ -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];

View file

@ -59,8 +59,6 @@
Content="{ext:Locale SetupWizardActionBack}"
Margin="10,0,0,0"
Command="{Binding MoveBackCommand}" />
<StackPanel Orientation="Horizontal">
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"

View file

@ -65,10 +65,11 @@ namespace Ryujinx.Ava.UI.Helpers
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
}
public static void ShowError(string message) =>
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) =>

View file

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:markup="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:pages="clr-namespace:Ryujinx.UI.SetupWizard.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="pages:SetupFirmwarePageViewModel"
x:Class="Ryujinx.UI.SetupWizard.Pages.SetupFirmwarePage">
<StackPanel>
<TextBlock Text="{markup:Locale SetupWizardFirmwarePageDescription}"/>
<Grid ColumnDefinitions="*" RowDefinitions="*,Auto">
<TextBox Name="FirmwarePathField" Margin="0, 10, 0, 5" Text="{Binding FirmwareSourcePath}" IsReadOnly="True" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="3.5">
<Button
Content="{markup:Locale SetupWizardFirmwarePageFolderBrowse}"
Command="{Binding BrowseFolderCommand}"
CommandParameter="{Binding #FirmwarePathField}"/>
<Button
Content="{markup:Locale SetupWizardFirmwarePageFileBrowse}"
Command="{Binding BrowseFileCommand}"
CommandParameter="{Binding #FirmwarePathField}"/>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using Ryujinx.Ava.UI.Controls;
namespace Ryujinx.UI.SetupWizard.Pages
{
public partial class SetupFirmwarePage : RyujinxControl<SetupFirmwarePageViewModel>
{
public SetupFirmwarePage()
{
InitializeComponent();
}
}
}

View file

@ -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<IStorageFile> result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SetupWizardFirmwarePageFilePopupTitle],
FileTypeFilter = new List<FilePickerFileType>
{
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<IStorageFolder> result = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SetupWizardFirmwarePageFolderPopupTitle]
});
if (result.TryGet(out IStorageFolder firmwareFolder))
{
tb.Text = firmwareFolder.TryGetLocalPath();
}
}
}
}

View file

@ -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<IStorageFolder> 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();
}
}
}

View file

@ -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)

View file

@ -3,27 +3,49 @@ 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<bool> SetupKeys()
{
if (!mwvm.VirtualFileSystem.HasKeySet)
{
Retry:
@ -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;
return true;
}
private async ValueTask<bool> SetupFirmware()
{
if (!HasFirmware)
{
Retry:
SetupKeysPageViewModel kpvm = new();
SetupFirmwarePageViewModel fwvm = new();
bool result = await NextPage()
.WithTitle(LocaleKeys.SetupWizardKeysPageTitle)
.WithContent<SetupKeysPage>(kpvm)
.WithTitle(LocaleKeys.SetupWizardFirmwarePageTitle)
.WithContent<SetupFirmwarePage>(fwvm)
.Show();
if (!result)
goto Keys;
return false;
if (!Directory.Exists(kpvm.KeysFolderPath))
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;
}
}
}

View file

@ -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)
{

View file

@ -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;
}
});