diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 49ee5a01d..d165bd742 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -2297,6 +2297,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": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "GameListContextMenuExtractDataLogo", "Translations": { @@ -22896,6 +22946,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": "", + "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 88a991a3d..61c5cfe00 100644 --- a/src/Ryujinx/Common/ApplicationHelper.cs +++ b/src/Ryujinx/Common/ApplicationHelper.cs @@ -296,13 +296,13 @@ namespace Ryujinx.Ava.Common extractorThread.Start(); } - public static void ExtractAoc(string destination, NcaSectionType ncaSectionType, string updateFilePath, string updateName) + 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, Path.GetFileName(updateFilePath)), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, NcaSectionType.Data, Path.GetFileName(updateFilePath)), cancellationToken); Thread extractorThread = new(() => @@ -352,7 +352,7 @@ namespace Ryujinx.Ava.Common ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; - int index = Nca.GetSectionIndexFromType(ncaSectionType, publicDataNca.Header.ContentType); + int index = Nca.GetSectionIndexFromType(NcaSectionType.Data, publicDataNca.Header.ContentType); try { @@ -410,14 +410,13 @@ namespace Ryujinx.Ava.Common } }) { - Name = "GUI.NcaSectionExtractorThread", + Name = "GUI.AocExtractorThread", IsBackground = true, }; extractorThread.Start(); } - public static async Task ExtractAoc(IStorageProvider storageProvider, NcaSectionType ncaSectionType, - string updateFilePath, string updateName) + public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName) { var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { @@ -430,7 +429,7 @@ namespace Ryujinx.Ava.Common return; } - ExtractAoc(result[0].Path.LocalPath, ncaSectionType, updateFilePath, updateName); + ExtractAoc(result[0].Path.LocalPath, updateFilePath, updateName); } 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/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..c2144da2d --- /dev/null +++ b/src/Ryujinx/UI/Controls/DlcSelectView.axaml.cs @@ -0,0 +1,55 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common; +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 Ryujinx.Ava.Utilities.Compat; +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/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/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml index 8270e070a..e2c4fe16e 100644 --- a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml @@ -7,7 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:pi="using:Projektanker.Icons.Avalonia" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:models="clr-namespace:Ryujinx.Ava.Common.Models" Width="500" @@ -15,9 +14,6 @@ mc:Ignorable="d" x:DataType="viewModels:DownloadableContentManagerViewModel" Focusable="True"> - - - - + @@ -113,22 +109,13 @@ Margin="10 0" HorizontalAlignment="Left" VerticalAlignment="Center" - Text="{Binding TitleId}" /> + Text="{Binding TitleIdStr}" /> -