game dir setup

known bugs are a missing prod.keys popup after setup (how), as well as the dialog for autoload kinda cluttering up the screen after you hit next on the game dir page
This commit is contained in:
GreemDev 2025-12-07 00:27:46 -06:00
parent ba9334e73d
commit e2f406f070
10 changed files with 347 additions and 13 deletions

View file

@ -25217,6 +25217,31 @@
"zh_TW": ""
}
},
{
"ID": "SetupWizardGameDirsPageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Game, Update, and DLC Paths",
"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": "SetupWizardFinalPageTitle",
"Translations": {

View file

@ -19,6 +19,9 @@ namespace Ryujinx.Common
public const string DumpFirmwareWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Dumping/Firmware";
public const string DumpContentWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Dumping/Games,-Updates-&-DLC";
public const string MultiplayerWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
}

View file

@ -99,6 +99,14 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
public override Result CompleteStep()
{
if (string.IsNullOrEmpty(FirmwareSourcePath) && RyujinxSetupWizard.HasFirmware)
{
NotificationManager.Information(
title: LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
"Skipped setting up firmware as you already have a valid firmware installation and did not choose a folder or file to install from.\n\nClick 'Back' if you wish to overwrite your firmware.");
return Result.Success; // This handles the user selecting no file/dir and just hitting Next.
}
if (!Directory.Exists(FirmwareSourcePath))
return Result.Fail;

View file

@ -0,0 +1,107 @@
<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:pages="clr-namespace:Ryujinx.UI.SetupWizard.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.UI.SetupWizard.Pages.SetupGameDirsPage"
x:DataType="pages:SetupGameDirsPageContext">
<StackPanel
Margin="10"
Spacing="10"
Orientation="Vertical" HorizontalAlignment="Stretch">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="GameDirsList"
MinHeight="120"
ItemsSource="{Binding GameDirs}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</ListBox.Styles>
</ListBox>
<Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<TextBox
Name="GameDirPathBox"
Margin="0"
Watermark="{ext:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddGameDirButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveGameDirButton"
Grid.Column="2"
MinWidth="90"
Margin="5,0,0,0"
Click="RemoveGameDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralRemove}" />
</Button>
</Grid>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
</StackPanel>
<TextBlock Foreground="{DynamicResource SecondaryTextColor}"
Text="{ext:Locale SettingsTabGeneralAutoloadNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="AutoloadDirsList"
MinHeight="100"
ItemsSource="{Binding UpdateAndDlcDirs}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</ListBox.Styles>
</ListBox>
<Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<TextBox
Name="AutoloadDirPathBox"
Margin="0"
Watermark="{ext:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddAutoloadDirButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveAutoloadDirButton"
Grid.Column="2"
MinWidth="90"
Margin="5,0,0,0"
Click="RemoveAutoloadDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralRemove}" />
</Button>
</Grid>
</StackPanel>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,80 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Ryujinx.Ava;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.UI.SetupWizard.Pages
{
public partial class SetupGameDirsPage : RyujinxControl<SetupGameDirsPageContext>
{
public SetupGameDirsPage()
{
InitializeComponent();
AddGameDirButton.Command =
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirs));
AddAutoloadDirButton.Command =
Commands.Create(() => AddDirButton(AutoloadDirPathBox, ViewModel.UpdateAndDlcDirs));
}
private async Task AddDirButton(TextBox addDirBox, ObservableCollection<string> directories)
{
string path = addDirBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !directories.Contains(path))
{
directories.Add(path);
addDirBox.Clear();
}
else
{
Gommon.Optional<IStorageFolder> folder = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync();
if (folder.HasValue)
{
directories.Add(folder.Value.Path.LocalPath);
}
}
}
private void RemoveGameDirButton_OnClick(object sender, RoutedEventArgs e)
{
int oldIndex = GameDirsList.SelectedIndex;
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
{
ViewModel.GameDirs.Remove(path);
}
if (GameDirsList.ItemCount > 0)
{
GameDirsList.SelectedIndex = oldIndex < GameDirsList.ItemCount ? oldIndex : 0;
}
}
private void RemoveAutoloadDirButton_OnClick(object sender, RoutedEventArgs e)
{
int oldIndex = AutoloadDirsList.SelectedIndex;
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
{
ViewModel.UpdateAndDlcDirs.Remove(path);
}
if (AutoloadDirsList.ItemCount > 0)
{
AutoloadDirsList.SelectedIndex = oldIndex < AutoloadDirsList.ItemCount ? oldIndex : 0;
}
}
}
}

View file

@ -0,0 +1,73 @@
using Avalonia.Controls;
using Avalonia.Layout;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using Gommon;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.SetupWizard;
using Ryujinx.Common;
using System;
using System.Linq;
namespace Ryujinx.UI.SetupWizard.Pages
{
public partial class SetupGameDirsPageContext() : SetupWizardPageContext(LocaleKeys.SetupWizardGameDirsPageTitle)
{
[ObservableProperty]
public partial ObservableCollection<string> GameDirs { get; set; }
= new(ConfigurationState.Instance.UI.GameDirs);
[ObservableProperty]
public partial ObservableCollection<string> UpdateAndDlcDirs { get; set; }
= new(ConfigurationState.Instance.UI.AutoloadDirs);
public override Result CompleteStep()
{
if (GameDirs.Count is 0)
{
NotificationManager.Error("At least one folder for games must be selected; otherwise the UI will be empty.");
return Result.Failure(RetryError.Shared);
}
ConfigurationState.Instance.UI.GameDirs.Value = GameDirs.ToList();
ConfigurationState.Instance.UI.AutoloadDirs.Value = UpdateAndDlcDirs.ToList();
OwningWizard.SignalConfigModified();
RyujinxApp.MainWindow.LoadApplications();
return Result.Success;
}
public override object CreateHelpContent()
{
Grid grid = new()
{
RowDefinitions = [new(GridLength.Auto), new(GridLength.Auto)],
HorizontalAlignment = HorizontalAlignment.Center
};
grid.Children.Add(new TextBlock
{
Text = "Not sure how to get your games, updates, and/or DLC onto your PC?",
HorizontalAlignment = HorizontalAlignment.Center,
GridRow = 0
});
grid.Children.Add(new HyperlinkButton
{
Content = "Click here to view a guide.",
HorizontalAlignment = HorizontalAlignment.Center,
NavigateUri = new Uri(SharedConstants.DumpFirmwareWikiUrl),
GridRow = 1
});
return grid;
}
}
public struct RetryError : IErrorState
{
public static readonly RetryError Shared = new();
}
}

View file

@ -21,11 +21,6 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
{
public partial class SetupKeysPageContext() : SetupWizardPageContext(LocaleKeys.SetupWizardKeysPageTitle)
{
public override Result CompleteStep() =>
Directory.Exists(KeysFolderPath)
? InstallKeys(KeysFolderPath)
: Result.Fail;
public override object CreateHelpContent()
{
Grid grid = new()
@ -70,8 +65,19 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
}
}
private Result InstallKeys(string directory)
public override Result CompleteStep()
{
if (string.IsNullOrEmpty(KeysFolderPath) && RyujinxApp.MainWindow.VirtualFileSystem.HasKeySet)
{
NotificationManager.Information(
title: LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
"Skipped setting up keys as you already have a valid key installation and did not choose a folder to install from.\n\nClick 'Back' if you wish to reinstall your keys.");
return Result.Success; // This handles the user selecting no folder and just hitting Next.
}
if (!Directory.Exists(KeysFolderPath))
return Result.Fail;
try
{
string systemDirectory = AppDataManager.KeysDirPath;
@ -81,9 +87,9 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
systemDirectory = AppDataManager.KeysDirPathUser;
}
Logger.Info?.Print(LogClass.Application, $"Installing keys from {directory}");
Logger.Info?.Print(LogClass.Application, $"Installing keys from {KeysFolderPath}");
ContentManager.InstallKeys(directory, systemDirectory);
ContentManager.InstallKeys(KeysFolderPath, systemDirectory);
NotificationManager.Information(
title: LocaleManager.Instance[LocaleKeys.RyujinxInfo],
@ -105,7 +111,7 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages
if (ex is FormatException)
{
message = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, directory);
LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, KeysFolderPath);
}
NotificationManager.Error(message, waitingExit: true);

View file

@ -1,5 +1,5 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.SetupWizard.Pages;
using Ryujinx.UI.SetupWizard.Pages;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.SetupWizard
@ -51,6 +51,34 @@ namespace Ryujinx.Ava.UI.SetupWizard
return true;
}
private async ValueTask<bool> SetupGameDirs()
{
if (!HasFirmware)
{
NotificationManager.Error("Firmware still seems to not be installed. Please try again.");
return false;
}
Retry:
bool result =
await NextPage<SetupGameDirsPage, SetupGameDirsPageContext>(out SetupGameDirsPageContext gdContext)
.Show();
if (!result)
return false;
var res = gdContext.CompleteStep();
if (res.IsOf<RetryError>())
return false;
if (!res)
goto Retry;
return true;
}
private ValueTask<bool> Finish()
=> NextPage<SetupFinishedPage, SetupFinishedPageContext>(out _)
.WithHelpButtonVisible(false)

View file

@ -82,9 +82,13 @@ namespace Ryujinx.Ava.UI.SetupWizard
Firmware:
if (!await SetupFirmware())
goto Keys;
GameDirs:
if (!await SetupGameDirs())
goto Firmware;
if (!await Finish())
goto Firmware;
goto GameDirs;
Return:
if (_configWasModified)

View file

@ -40,8 +40,8 @@ namespace Ryujinx.Ava.UI.SetupWizard
{
RyujinxSetupWizardWindow window = new();
window.DataContext = setupWizard = new RyujinxSetupWizard(window, overwriteMode);
window.Height = 600;
window.Width = 750;
window.Height = 700;
window.Width = 825;
return window;
}