Initial work on a setup wizard

Setup wizard abstraction & architecture from TKMM
This commit is contained in:
GreemDev 2025-11-21 00:20:15 -06:00
parent 52700f71dc
commit aee46e16cd
19 changed files with 743 additions and 10 deletions

View file

@ -24841,6 +24841,206 @@
"zh_CN": "如果您在设置中有某些 LDN 口令则可加入此游戏。", "zh_CN": "如果您在设置中有某些 LDN 口令则可加入此游戏。",
"zh_TW": "你只能加入與 LDN 網路密碼片語 (passphrase) 設定相同的遊戲。" "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": ""
}
} }
] ]
} }

View file

@ -219,6 +219,8 @@ namespace Ryujinx.HLE.FileSystem
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
} }
public bool HasKeySet { get; private set; }
public void ReloadKeySet() public void ReloadKeySet()
{ {
KeySet ??= KeySet.CreateDefaultKeySet(); KeySet ??= KeySet.CreateDefaultKeySet();
@ -235,6 +237,8 @@ namespace Ryujinx.HLE.FileSystem
LoadSetAtPath(AppDataManager.KeysDirPath); LoadSetAtPath(AppDataManager.KeysDirPath);
HasKeySet = (prodKeyFile != null && titleKeyFile != null) || prodKeyFile != null;
void LoadSetAtPath(string basePath) void LoadSetAtPath(string basePath)
{ {
string localProdKeyFile = Path.Combine(basePath, "prod.keys"); 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) public void ImportTickets(IFileSystem fs)

View file

@ -32,6 +32,8 @@ namespace Ryujinx.Ava
{ {
internal static class Program internal static class Program
{ {
public static bool IsFirstStart { get; set; }
public static double WindowScaleFactor { get; set; } public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0; public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; } public static string Version { get; private set; }
@ -221,7 +223,6 @@ namespace Ryujinx.Ava
public static void ReloadConfig(bool isRunGameWithCustomConfig = false) public static void ReloadConfig(bool isRunGameWithCustomConfig = false)
{ {
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
@ -247,6 +248,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
IsFirstStart = true;
} }
else else
{ {

View file

@ -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)
{
/// <summary>
/// Define the logic and flow of this <see cref="BaseSetupWizard"/>.
/// </summary>
public abstract ValueTask Start();
protected ValueTask<bool> 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);
}
}
}

View file

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

View file

@ -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<bool> Show(ContentPresenter presenter)
{
presenter.Content = new SetupWizardPageView
{
DataContext = this,
};
try {
await Task.Delay(-1, _cancellationTokenSource.Token);
}
catch (TaskCanceledException) {
return _result ?? false;
}
return false;
}
}
}

View file

@ -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<TControl>(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<bool> Show()
{
return _page.Show(presenter);
}
}
}

View file

@ -0,0 +1,77 @@
<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:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:fa="using:Projektanker.Icons.Avalonia"
xmlns:wiz="using:Ryujinx.Ava.Systems.SetupWizard"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="wiz:SetupWizardPage"
x:Class="Ryujinx.Systems.SetupWizard.SetupWizardPageView">
<Grid RowDefinitions="*,Auto" Margin="60">
<ScrollViewer>
<Grid RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0"
TextWrapping="WrapWithOverflow"
FontFamily="{StaticResource HyliaGlyph}"
FontSize="46"
Text="{Binding Title}" />
<ContentPresenter Grid.Row="1"
Content="{Binding}"
IsVisible="{Binding !#InfoToggle.IsChecked}"
TextWrapping="WrapWithOverflow"
Margin="0,15,0,0">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="{x:Type wiz:SetupWizardPage}">
<ContentControl Content="{Binding Content}" VerticalAlignment="Stretch"/>
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
<Grid Grid.Row="2"
ColumnDefinitions="Auto,*"
IsVisible="{Binding #InfoToggle.IsChecked}">
<StackPanel Spacing="5" VerticalAlignment="Top">
<HyperlinkButton NavigateUri="https://discord.gg/PEuzjrFXUA" Content="Join Discord" />
</StackPanel>
<ContentPresenter Content="{Binding}"
Grid.Column="1"
Margin="20,0,0,0"
TextWrapping="WrapWithOverflow">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="{x:Type wiz:SetupWizardPage}">
<ContentControl Content="{Binding HelpContent}" />
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
</Grid>
</Grid>
</ScrollViewer>
<Grid ColumnDefinitions="Auto,Auto,*" Grid.Row="1">
<ToggleButton Name="InfoToggle"
Padding="6">
<fa:Icon Value="fa-solid fa-circle-info" />
</ToggleButton>
<Button IsVisible="{Binding !IsFirstPage}"
Grid.Column="1"
Content="{ext:Locale SetupWizardActionBack}"
Margin="10,0,0,0"
Command="{Binding MoveBackCommand}" />
<StackPanel Orientation="Horizontal">
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Column="2">
<Button Content="{Binding ActionContent}"
Command="{Binding MoveNextCommand}" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

View file

@ -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<SetupWizardPage>
{
public SetupWizardPageView()
{
InitializeComponent();
}
}
}

View file

@ -15,6 +15,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.UI.SetupWizard;
using System; using System;
using System.Diagnostics; using System.Diagnostics;

View file

@ -0,0 +1,22 @@
<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:pages="clr-namespace:Ryujinx.UI.SetupWizard.Pages"
xmlns:markup="clr-namespace:Ryujinx.Ava.Common.Markup"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.UI.SetupWizard.Pages.SetupKeysPage"
x:DataType="pages:SetupKeysPageViewModel">
<StackPanel>
<TextBlock Text="{markup:Locale SetupWizardKeysPageDescription}" Margin="0,0,0,10"/>
<Grid ColumnDefinitions="*,Auto">
<TextBox Name="KeysFolderPathField" Text="{Binding KeysFolderPath}" IsReadOnly="True" />
<Button Grid.Column="1"
Content="..."
Command="{Binding BrowseCommand}"
CommandParameter="{Binding #KeysFolderPathField}"
Margin="5,0,0,0"/>
</Grid>
</StackPanel>
</UserControl>

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
<windows:StyleableAppWindow 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:windows="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:setupWizard="clr-namespace:Ryujinx.Ava.UI.SetupWizard"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
CanResize="False"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.UI.SetupWizard.RyujinxSetupWizardWindow"
x:DataType="setupWizard:RyujinxSetupWizard"
Title="{markup:Locale SetupWizardFirstPageTitle}">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" RowDefinitions="Auto,*">
<Grid Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Name="FlushControls">
<controls:RyujinxLogo ToolTip.Tip="{markup:Locale SetupWizardFirstPageTitle}"/>
</Grid>
<ContentPresenter Grid.Row="1" Name="WizardPresenter"/>
</Grid>
</windows:StyleableAppWindow>

View file

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

View file

@ -45,6 +45,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.UI.SetupWizard;
using SkiaSharp; using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -870,7 +871,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void RefreshGrid() private void RefreshGrid()
{ {
IObservableList<ApplicationData> appsList = Applications.ToObservableChangeSet() _ = Applications.ToObservableChangeSet()
.Filter(Filter) .Filter(Filter)
.Sort(GetComparer()) .Sort(GetComparer())
.Bind(out ReadOnlyObservableCollection<ApplicationData> apps) .Bind(out ReadOnlyObservableCollection<ApplicationData> apps)
@ -1013,7 +1014,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
private async Task HandleKeysInstallation(string filename) public async Task HandleKeysInstallation(string filename)
{ {
try try
{ {

View file

@ -18,7 +18,6 @@
<viewModels:CompatibilityViewModel /> <viewModels:CompatibilityViewModel />
</window:StyleableAppWindow.DataContext> </window:StyleableAppWindow.DataContext>
<Grid RowDefinitions="Auto,Auto,*"> <Grid RowDefinitions="Auto,Auto,*">
<!-- UI FlushControls --> <!-- UI FlushControls -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls"> <Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls">
<controls:RyujinxLogo <controls:RyujinxLogo

View file

@ -30,6 +30,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL3; using Ryujinx.Input.SDL3;
using Ryujinx.UI.SetupWizard;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -137,8 +138,19 @@ namespace Ryujinx.Ava.UI.Windows
NotificationHelper.SetNotificationManager(this); NotificationHelper.SetNotificationManager(this);
Executor.ExecuteBackgroundAsync(async () => Executor.ExecuteBackgroundAsync(async () =>
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ShowIntelMacWarningAsync(); 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 (CommandLineState.FirmwareToInstallPathArg.TryGet(out FilePath fwPath))
{ {
if (fwPath is { ExistsAsFile: true, Extension: "xci" or "zip" } || fwPath.ExistsAsDirectory) if (fwPath is { ExistsAsFile: true, Extension: "xci" or "zip" } || fwPath.ExistsAsDirectory)
@ -150,6 +162,8 @@ namespace Ryujinx.Ava.UI.Windows
else else
Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file."); 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; ShowKeyErrorOnLoad = false;
@ -538,8 +552,6 @@ namespace Ryujinx.Ava.UI.Windows
{ {
LoadApplications(); LoadApplications();
} }
_ = CheckLaunchState();
} }
private void SetMainContent(Control content = null) private void SetMainContent(Control content = null)