diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs index aa57a0310..6fdfe1398 100644 --- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs @@ -56,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, _serializerContext.ProfilesJson); return profilesJson.Profiles - .FindFirst(profile => profile.AccountState == AccountState.Open) + .FindFirst(profile => profile.UserId == profilesJson.LastOpened) .Convert(profileJson => new UserProfile(new UserId(profileJson.UserId), profileJson.Name, profileJson.Image, profileJson.LastModifiedTimestamp)); } diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs index 65748be33..846c4dc4f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -244,6 +244,15 @@ namespace Ryujinx.HLE.HOS.Services.Settings return ResultCode.Success; } + [CommandCmif(68)] + // GetSerialNumber() -> buffer + public ResultCode GetSerialNumber(ServiceCtx context) + { + context.ResponseData.Write(Encoding.ASCII.GetBytes("RYU00000000000")); + + return ResultCode.Success; + } + [CommandCmif(77)] // GetDeviceNickName() -> buffer public ResultCode GetDeviceNickName(ServiceCtx context) diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs index 4a2a910f8..ade67b9c0 100644 --- a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs @@ -51,6 +51,9 @@ namespace Ryujinx.HLE.HOS.Services.Spl context.ResponseData.Write(configValue); + if (result == SmcResult.Success) + return ResultCode.Success; + return (ResultCode)((int)result << 9) | ResultCode.ModuleId; } diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 49ee5a01d..39897c240 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -748,32 +748,7 @@ } }, { - "ID": "MenuBarTools", - "Translations": { - "ar_SA": "_الأدوات", - "de_DE": "", - "el_GR": "_Εργαλεία", - "en_US": "_Tools", - "es_ES": "_Herramientas", - "fr_FR": "_Outils", - "he_IL": "_כלים", - "it_IT": "_Strumenti", - "ja_JP": "ツール(_T)", - "ko_KR": "도구(_T)", - "no_NO": "_Verktøy", - "pl_PL": "_Narzędzia", - "pt_BR": "_Ferramentas", - "ru_RU": "_Инструменты", - "sv_SE": "V_erktyg", - "th_TH": "_เครื่องมือ", - "tr_TR": "_Araçlar", - "uk_UA": "_Інструменти", - "zh_CN": "工具(_T)", - "zh_TW": "工具(_T)" - } - }, - { - "ID": "MenuBarToolsInstallFirmware", + "ID": "MenuBarActionsInstallFirmware", "Translations": { "ar_SA": "تثبيت البرنامج الثابت", "de_DE": "Firmware installieren", @@ -798,7 +773,7 @@ } }, { - "ID": "MenuBarFileToolsInstallFirmwareFromFile", + "ID": "MenuBarActionsInstallFirmwareFromFile", "Translations": { "ar_SA": "تثبيت برنامج ثابت من XCI أو ZIP", "de_DE": "Firmware von einer XCI- oder einer ZIP-Datei installieren", @@ -823,7 +798,7 @@ } }, { - "ID": "MenuBarFileToolsInstallFirmwareFromDirectory", + "ID": "MenuBarActionsInstallFirmwareFromDirectory", "Translations": { "ar_SA": "تثبيت برنامج ثابت من مجلد", "de_DE": "Firmware aus einem Verzeichnis installieren", @@ -848,7 +823,7 @@ } }, { - "ID": "MenuBarToolsInstallKeys", + "ID": "MenuBarActionsInstallKeys", "Translations": { "ar_SA": "", "de_DE": "", @@ -873,7 +848,7 @@ } }, { - "ID": "MenuBarFileToolsInstallKeysFromFile", + "ID": "MenuBarFileActionsInstallKeysFromFile", "Translations": { "ar_SA": "", "de_DE": "", @@ -898,7 +873,7 @@ } }, { - "ID": "MenuBarFileToolsInstallKeysFromFolder", + "ID": "MenuBarFileActionsInstallKeysFromFolder", "Translations": { "ar_SA": "", "de_DE": "", @@ -923,7 +898,7 @@ } }, { - "ID": "MenuBarToolsManageFileTypes", + "ID": "MenuBarActionsManageFileTypes", "Translations": { "ar_SA": "إدارة أنواع الملفات", "de_DE": "Dateitypen verwalten", @@ -948,7 +923,7 @@ } }, { - "ID": "MenuBarToolsInstallFileTypes", + "ID": "MenuBarActionsInstallFileTypes", "Translations": { "ar_SA": "تثبيت أنواع الملفات", "de_DE": "Dateitypen installieren", @@ -973,7 +948,7 @@ } }, { - "ID": "MenuBarToolsUninstallFileTypes", + "ID": "MenuBarActionsUninstallFileTypes", "Translations": { "ar_SA": "إزالة أنواع الملفات", "de_DE": "Dateitypen deinstallieren", @@ -998,7 +973,7 @@ } }, { - "ID": "MenuBarToolsXCITrimmer", + "ID": "MenuBarActionsXCITrimmer", "Translations": { "ar_SA": "", "de_DE": "", @@ -2297,6 +2272,56 @@ "zh_TW": "從應用程式的目前配置中提取 RomFS 分區 (包含更新)" } }, + { + "ID": "GameListContextMenuExtractDataAocRomFS", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "DLC RomFS", + "es_ES": "", + "fr_FR": "RomFS de DLC", + "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": "GameListContextMenuExtractDataAocRomFSToolTip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Extract the RomFS from a selected DLC file", + "es_ES": "", + "fr_FR": "Extraire les RomFS d'un fichier DLC choisi", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "Pakk ut RomFS filene fra valgt DLC fil", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "GameListContextMenuExtractDataLogo", "Translations": { @@ -22896,6 +22921,31 @@ "zh_CN": "什么都没有", "zh_TW": "無法啟動 (Nothing)" } + }, + { + "ID": "ExtractAocListHeader", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Select a DLC to Extract", + "es_ES": "", + "fr_FR": "Choisissez un DLC à extraire", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "Velg en DLC og hente ut", + "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/Common/ApplicationHelper.cs b/src/Ryujinx/Common/ApplicationHelper.cs index 7db933ed6..61c5cfe00 100644 --- a/src/Ryujinx/Common/ApplicationHelper.cs +++ b/src/Ryujinx/Common/ApplicationHelper.cs @@ -295,6 +295,142 @@ namespace Ryujinx.Ava.Common }; extractorThread.Start(); } + + public static void ExtractAoc(string destination, string updateFilePath, string updateName) + { + var cancellationToken = new CancellationTokenSource(); + + UpdateWaitWindow waitingDialog = new( + RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, NcaSectionType.Data, Path.GetFileName(updateFilePath)), + cancellationToken); + + Thread extractorThread = new(() => + { + Dispatcher.UIThread.Post(waitingDialog.Show); + + using FileStream file = new(updateFilePath, FileMode.Open, FileAccess.Read); + + Nca publicDataNca = null; + + string extension = Path.GetExtension(updateFilePath).ToLower(); + if (extension is ".nsp") + { + var pfsTemp = new PartitionFileSystem(); + pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure(); + IFileSystem pfs = pfsTemp; + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); + if (nca.Header.ContentType is NcaContentType.PublicData && nca.SectionExists(NcaSectionType.Data)) + { + publicDataNca = nca; + } + } + } + + if (publicDataNca is null) + { + Logger.Error?.Print(LogClass.Application, "Extraction failure. The NCA was not present in the selected file"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]); + }); + + return; + } + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + int index = Nca.GetSectionIndexFromType(NcaSectionType.Data, publicDataNca.Header.ContentType); + + try + { + IFileSystem ncaFileSystem = publicDataNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); + + FileSystemClient fsClient = _horizonClient.Fs; + + string source = DateTime.Now.ToFileTime().ToString()[10..]; + string output = DateTime.Now.ToFileTime().ToString()[10..]; + + using var uniqueSourceFs = new UniqueRef(ncaFileSystem); + using var uniqueOutputFs = new UniqueRef(new LocalFileSystem(destination)); + + fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); + fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); + + (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token); + + if (!canceled) + { + if (resultCode.Value.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]); + }); + } + else if (resultCode.Value.IsSuccess()) + { + Dispatcher.UIThread.Post(waitingDialog.Close); + + NotificationHelper.ShowInformation( + RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle), + $"{updateName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}"); + } + } + + fsClient.Unmount(source.ToU8Span()); + fsClient.Unmount(output.ToU8Span()); + } + catch (ArgumentException ex) + { + Logger.Error?.Print(LogClass.Application, $"{ex.Message}"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(ex.Message); + }); + } + }) + { + Name = "GUI.AocExtractorThread", + IsBackground = true, + }; + extractorThread.Start(); + } + + public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName) + { + var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], + AllowMultiple = false, + }); + + if (result.Count == 0) + { + return; + } + + ExtractAoc(result[0].Path.LocalPath, updateFilePath, updateName); + } public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0) diff --git a/src/Ryujinx/Common/Models/DownloadableContentModel.cs b/src/Ryujinx/Common/Models/DownloadableContentModel.cs index ad9934bd2..de7a334ee 100644 --- a/src/Ryujinx/Common/Models/DownloadableContentModel.cs +++ b/src/Ryujinx/Common/Models/DownloadableContentModel.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Ava.Common.Models public bool IsBundled { get; } = System.IO.Path.GetExtension(ContainerPath)?.ToLower() == ".xci"; public string FileName => System.IO.Path.GetFileName(ContainerPath); - public string TitleIdStr => TitleId.ToString("x16"); + public string TitleIdStr => TitleId.ToString("x16").ToUpper(); public ulong TitleIdBase => TitleId & ~0x1FFFUL; } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index b9aa402c5..52b2d655d 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -230,6 +230,7 @@ namespace Ryujinx.Ava internal static void PrintSystemInfo() { Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}"); + Logger.Notice.Print(LogClass.Application, $".NET Runtime: {RuntimeInformation.FrameworkDescription}"); SystemInfo.Gather().Print(); var enabledLogLevels = Logger.GetEnabledLevels().ToArray(); diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 8c72d5a3c..7d342812f 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -173,10 +173,5 @@ - - - UserSelectorDialog.axaml - Code - - + diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml index 7708936ca..9fed95aa7 100644 --- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -106,6 +106,10 @@ Click="ExtractApplicationRomFs_Click" Header="{ext:Locale GameListContextMenuExtractDataRomFS}" ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Controls/DlcSelectView.axaml.cs b/src/Ryujinx/UI/Controls/DlcSelectView.axaml.cs new file mode 100644 index 000000000..c011ca110 --- /dev/null +++ b/src/Ryujinx/UI/Controls/DlcSelectView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Common.Models; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.Utilities.AppLibrary; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class DlcSelectView : UserControl + { + public DlcSelectView() + { + InitializeComponent(); + } + +#nullable enable + public static async Task Show(ulong selectedTitleId, ApplicationLibrary appLibrary) +#nullable disable + { + DlcSelectViewModel viewModel = new(selectedTitleId, appLibrary); + + ContentDialog contentDialog = new() + { + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue], + SecondaryButtonText = string.Empty, + CloseButtonText = string.Empty, + Content = new DlcSelectView { DataContext = viewModel } + }; + + Style closeButton = new(x => x.Name("CloseButton")); + closeButton.Setters.Add(new Setter(WidthProperty, 80d)); + + Style closeButtonParent = new(x => x.Name("CommandSpace")); + closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, + Avalonia.Layout.HorizontalAlignment.Right)); + + contentDialog.Styles.Add(closeButton); + contentDialog.Styles.Add(closeButtonParent); + + await ContentDialogHelper.ShowAsync(contentDialog); + + return viewModel.SelectedDlc; + } + } +} diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs index c27a3ac9b..4d021655e 100644 --- a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs +++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs @@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Controls VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient) { - var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); + NavigationDialogHost content = new(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); ContentDialog contentDialog = new() { Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], diff --git a/src/Ryujinx/UI/Helpers/Commands.cs b/src/Ryujinx/UI/Helpers/Commands.cs index df24a7da1..868b49158 100644 --- a/src/Ryujinx/UI/Helpers/Commands.cs +++ b/src/Ryujinx/UI/Helpers/Commands.cs @@ -12,9 +12,9 @@ namespace Ryujinx.Ava.UI.Helpers public static RelayCommand CreateConditional(Action action, Func canExecute) => new(action, canExecute); - public static RelayCommand CreateWithArg(Action action) + public static RelayCommand Create(Action action) => new(action); - public static RelayCommand CreateConditionalWithArg(Action action, Predicate canExecute) + public static RelayCommand CreateConditional(Action action, Predicate canExecute) => new(action, canExecute); public static AsyncRelayCommand Create(Func action) @@ -24,11 +24,11 @@ namespace Ryujinx.Ava.UI.Helpers public static AsyncRelayCommand CreateSilentFail(Func action) => new(action, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler); - public static AsyncRelayCommand CreateWithArg(Func action) + public static AsyncRelayCommand Create(Func action) => new(action, AsyncRelayCommandOptions.None); - public static AsyncRelayCommand CreateConcurrentWithArg(Func action) + public static AsyncRelayCommand CreateConcurrent(Func action) => new(action, AsyncRelayCommandOptions.AllowConcurrentExecutions); - public static AsyncRelayCommand CreateSilentFailWithArg(Func action) + public static AsyncRelayCommand CreateSilentFail(Func action) => new(action, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler); public static AsyncRelayCommand CreateConditional(Func action, Func canExecute) @@ -38,11 +38,11 @@ namespace Ryujinx.Ava.UI.Helpers public static AsyncRelayCommand CreateSilentFailConditional(Func action, Func canExecute) => new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler); - public static AsyncRelayCommand CreateConditionalWithArg(Func action, Predicate canExecute) + public static AsyncRelayCommand CreateConditional(Func action, Predicate canExecute) => new(action, canExecute, AsyncRelayCommandOptions.None); - public static AsyncRelayCommand CreateConcurrentConditionalWithArg(Func action, Predicate canExecute) + public static AsyncRelayCommand CreateConcurrentConditional(Func action, Predicate canExecute) => new(action, canExecute, AsyncRelayCommandOptions.AllowConcurrentExecutions); - public static AsyncRelayCommand CreateSilentFailConditionalWithArg(Func action, Predicate canExecute) + public static AsyncRelayCommand CreateSilentFailConditional(Func action, Predicate canExecute) => new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler); } } diff --git a/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs b/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs new file mode 100644 index 000000000..d50d8249a --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/DlcSelectViewModel.cs @@ -0,0 +1,25 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ryujinx.Ava.Common.Models; +using Ryujinx.Ava.Utilities.AppLibrary; +using System.Linq; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public partial class DlcSelectViewModel : BaseModel + { + [ObservableProperty] private DownloadableContentModel[] _dlcs; + #nullable enable + [ObservableProperty] private DownloadableContentModel? _selectedDlc; + #nullable disable + + public DlcSelectViewModel(ulong titleId, ApplicationLibrary appLibrary) + { + _dlcs = appLibrary.DownloadableContents.Items + .Where(x => x.Dlc.TitleIdBase == titleId) + .Select(x => x.Dlc) + .OrderBy(it => it.IsBundled ? 0 : 1) + .ThenBy(it => it.TitleId) + .ToArray(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index f88ed65d0..8193c323b 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -624,6 +624,7 @@ namespace Ryujinx.Ava.UI.ViewModels ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize], ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath], ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], + ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel], _ => string.Empty, }; } @@ -694,6 +695,7 @@ namespace Ryujinx.Ava.UI.ViewModels public IHostUIHandler UiHandler { get; internal set; } public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; public bool IsSortedByTitle => SortMode == ApplicationSort.Title; + public bool IsSortedByTitleId => SortMode == ApplicationSort.TitleId; public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; @@ -726,6 +728,7 @@ namespace Ryujinx.Ava.UI.ViewModels ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize), ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path), ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)), + ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id), _ => null, #pragma warning restore IDE0055 }; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 6bce21851..1cd06bb7b 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -208,7 +208,7 @@ Name="ActionsMenuItem" VerticalAlignment="Center" Header="{ext:Locale MenuBarActions}" - IsEnabled="{Binding IsGameRunning}"> + IsVisible="{Binding !EnableNonGameRunningControls}"> - - - - + + + + - - - + + + - - - + + + - + diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 521460012..9a63c022d 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -37,26 +37,20 @@ namespace Ryujinx.Ava.UI.Views.Main ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems(); ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems(); - MiiAppletMenuItem.Command = new AsyncRelayCommand(OpenMiiApplet); - CloseRyujinxMenuItem.Command = new RelayCommand(CloseWindow); - OpenSettingsMenuItem.Command = new AsyncRelayCommand(OpenSettings); - PauseEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Pause()); - ResumeEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Resume()); - StopEmulationMenuItem.Command = new AsyncRelayCommand(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted()); - CheatManagerMenuItem.Command = new AsyncRelayCommand(async () => - { - try - { - await OpenCheatManagerForCurrentApp(); - } catch {} - }); - InstallFileTypesMenuItem.Command = new AsyncRelayCommand(InstallFileTypes); - UninstallFileTypesMenuItem.Command = new AsyncRelayCommand(UninstallFileTypes); - XciTrimmerMenuItem.Command = new AsyncRelayCommand(() => XCITrimmerWindow.Show(ViewModel)); - AboutWindowMenuItem.Command = new AsyncRelayCommand(AboutWindow.Show); - CompatibilityListMenuItem.Command = new AsyncRelayCommand(CompatibilityList.Show); + MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet); + CloseRyujinxMenuItem.Command = Commands.Create(CloseWindow); + OpenSettingsMenuItem.Command = Commands.Create(OpenSettings); + PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause()); + ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume()); + StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted()); + CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp); + InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes); + UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes); + XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show); + AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show); + CompatibilityListMenuItem.Command = Commands.Create(CompatibilityList.Show); - UpdateMenuItem.Command = new AsyncRelayCommand(async () => + UpdateMenuItem.Command = Commands.Create(async () => { if (Updater.CanUpdate(true)) await Updater.BeginUpdateAsync(true); @@ -64,12 +58,12 @@ namespace Ryujinx.Ava.UI.Views.Main FaqMenuItem.Command = SetupGuideMenuItem.Command = - LdnGuideMenuItem.Command = new RelayCommand(OpenHelper.OpenUrl); + LdnGuideMenuItem.Command = Commands.Create(OpenHelper.OpenUrl); WindowSize720PMenuItem.Command = WindowSize1080PMenuItem.Command = WindowSize1440PMenuItem.Command = - WindowSize2160PMenuItem.Command = new RelayCommand(ChangeWindowSize); + WindowSize2160PMenuItem.Command = Commands.Create(ChangeWindowSize); } private IEnumerable GenerateToggleFileTypeItems() => diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml index 1c6895db1..cdc66a138 100644 --- a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml @@ -105,6 +105,12 @@ GroupName="Sort" IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" Tag="Title" /> + - - - - + @@ -101,7 +98,7 @@ TextWrapping="Wrap" TextTrimming="CharacterEllipsis"> - + @@ -112,7 +109,7 @@ Margin="10 0" HorizontalAlignment="Left" VerticalAlignment="Center" - Text="{Binding TitleId}" /> + Text="{Binding TitleIdStr}" />