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