From 4f5af0ecf3f02b954682d02062c7765947e0a024 Mon Sep 17 00:00:00 2001 From: Nicola <61830443+nicola02nb@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:53:08 +0100 Subject: [PATCH 1/3] Added Tool for installing keys --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 68 ++++++++ src/Ryujinx/Assets/Locales/en_US.json | 10 ++ .../UI/ViewModels/MainWindowViewModel.cs | 146 ++++++++++++++++++ .../UI/Views/Main/MainMenuBarView.axaml | 4 + 4 files changed, 228 insertions(+) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index fc8def9d2..31c293ebb 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -8,6 +8,7 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.Ncm; +using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; @@ -474,6 +475,56 @@ namespace Ryujinx.HLE.FileSystem FinishInstallation(temporaryDirectory, registeredDirectory); } + public void InstallKeys(string keysSource) + { + string systemDirectory = AppDataManager.KeysDirPath; + //if(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile) + //{ + // systemDirectory = AppDataManager.KeysDirPathUser; + //} + + if (Directory.Exists(keysSource)) + { + foreach (var filePath in Directory.EnumerateFiles(keysSource, "*.keys")) + { + File.Copy(filePath, Path.Combine(systemDirectory, Path.GetFileName(filePath)), true); + } + + return; + } + + if (!File.Exists(keysSource)) + { + throw new FileNotFoundException("Keys file does not exist."); + } + + FileInfo info = new(keysSource); + + using FileStream file = File.OpenRead(keysSource); + + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(keysSource)) + { + + foreach (var entry in archive.Entries) + { + if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase)) + { + entry.ExtractToFile(Path.Combine(systemDirectory, entry.Name), overwrite: true); + } + } + } + break; + case ".keys": + File.Copy(keysSource, Path.Combine(systemDirectory, info.Name), true); + break; + default: + throw new InvalidFirmwarePackageException("Input file is not a valid key package"); + } + } + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) { if (Directory.Exists(registeredDirectory)) @@ -947,5 +998,22 @@ namespace Ryujinx.HLE.FileSystem return null; } + + public bool AreKeysAlredyPresent() + { + if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")) && !File.Exists(Path.Combine(AppDataManager.KeysDirPath, "title.keys"))) + { + if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && (File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")) || File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "title.keys")))) + { + return true; + } + } + else + { + return true; + } + + return false; + } } } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index fdd2d4df2..53f865387 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -30,6 +30,9 @@ "MenuBarToolsInstallFirmware": "Install Firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarToolsInstallKeys": "Install Keys", + "MenuBarFileToolsInstallKeysFromFile": "Install keys from KEYS or ZIP", + "MenuBarFileToolsInstallKeysFromFolder": "Install keys from a directory", "MenuBarToolsManageFileTypes": "Manage file types", "MenuBarToolsInstallFileTypes": "Install file types", "MenuBarToolsUninstallFileTypes": "Uninstall file types", @@ -504,6 +507,13 @@ "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogKeysInstallerKeysNotFoundErrorMessage": "A valid Keys file was not found in {0}.", + "DialogKeysInstallerKeysInstallTitle": "Install Keys", + "DialogKeysInstallerKeysInstallMessage": "New Keys file will be installed.", + "DialogKeysInstallerKeysInstallSubMessage": "\n\nThis will replace some of the current installed Keys.", + "DialogKeysInstallerKeysInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogKeysInstallerKeysInstallWaitMessage": "Installing Keys...", + "DialogKeysInstallerKeysInstallSuccessMessage": "New Keys file successfully installed.", "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 53263847b..233e2d5bf 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1180,6 +1180,105 @@ namespace Ryujinx.Ava.UI.ViewModels } } + private async Task HandleKeysInstallation(string filename) + { + try + { + //bool isValidKeysFilke = true; + + //if (!isValidKeysFilke) + //{ + // await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename)); + + // return; + //} + + string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle); + string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); + + bool alreadyKesyInstalled = ContentManager.AreKeysAlredyPresent(); + if (alreadyKesyInstalled) + { + dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage); + } + + dialogMessage += LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + dialogTitle, + dialogMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]); + + if (result == UserResult.Yes) + { + Logger.Info?.Print(LogClass.Application, $"Installing Keys"); + + Thread thread = new(() => + { + Dispatcher.UIThread.InvokeAsync(delegate + { + waitingDialog.Show(); + }); + + try + { + ContentManager.InstallKeys(filename); + + Dispatcher.UIThread.InvokeAsync(async delegate + { + waitingDialog.Close(); + + string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSuccessMessage); + + await ContentDialogHelper.CreateInfoDialog( + dialogTitle, + message, + LocaleManager.Instance[LocaleKeys.InputDialogOk], + string.Empty, + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + Logger.Info?.Print(LogClass.Application, message); + }); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(ex.Message); + }); + } + finally + { + VirtualFileSystem.ReloadKeySet(); + } + }) + { + Name = "GUI.KeysInstallerThread", + }; + + thread.Start(); + } + } + catch (MissingKeyException ex) + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime) + { + Logger.Error?.Print(LogClass.Application, ex.ToString()); + + await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); + } + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(ex.Message); + } + } private void ProgressHandler(T state, int current, int total) where T : Enum { Dispatcher.UIThread.Post(() => @@ -1467,6 +1566,53 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public async Task InstallKeysFromFile() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes]) + { + Patterns = new[] { "*.keys", "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, + MimeTypes = new[] { "application/keys", "application/zip" }, + }, + new("KEYS") + { + Patterns = new[] { "*.keys" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, + MimeTypes = new[] { "application/keys" }, + }, + new("ZIP") + { + Patterns = new[] { "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, + MimeTypes = new[] { "application/zip" }, + }, + }, + }); + + if (result.Count > 0) + { + await HandleKeysInstallation(result[0].Path.LocalPath); + } + } + + public async Task InstallKeysFromFolder() + { + var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + AllowMultiple = false, + }); + + if (result.Count > 0) + { + await HandleKeysInstallation(result[0].Path.LocalPath); + } + } + public void OpenRyujinxFolder() { OpenHelper.OpenFolder(AppDataManager.BaseDirPath); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 883bf8971..1cc9f22cd 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -264,6 +264,10 @@ + + + + -- 2.47.1 From 9e921ae5c8eb15fbf78c1a4899d6b67500e2ec8f Mon Sep 17 00:00:00 2001 From: Nicola <61830443+nicola02nb@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:54:33 +0100 Subject: [PATCH 2/3] Implemented missing code sections --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 99 +++++++++++++------ src/Ryujinx/Assets/Locales/en_US.json | 4 +- .../UI/ViewModels/MainWindowViewModel.cs | 25 ++--- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index 31c293ebb..38386bc99 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -8,7 +8,6 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.Ncm; -using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; @@ -22,6 +21,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using Path = System.IO.Path; namespace Ryujinx.HLE.FileSystem @@ -475,19 +475,14 @@ namespace Ryujinx.HLE.FileSystem FinishInstallation(temporaryDirectory, registeredDirectory); } - public void InstallKeys(string keysSource) + public void InstallKeys(string keysSource, string installDirectory) { - string systemDirectory = AppDataManager.KeysDirPath; - //if(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile) - //{ - // systemDirectory = AppDataManager.KeysDirPathUser; - //} - if (Directory.Exists(keysSource)) { foreach (var filePath in Directory.EnumerateFiles(keysSource, "*.keys")) { - File.Copy(filePath, Path.Combine(systemDirectory, Path.GetFileName(filePath)), true); + VerifyKeysFile(filePath); + File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true); } return; @@ -507,24 +502,47 @@ namespace Ryujinx.HLE.FileSystem case ".zip": using (ZipArchive archive = ZipFile.OpenRead(keysSource)) { - - foreach (var entry in archive.Entries) - { - if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase)) - { - entry.ExtractToFile(Path.Combine(systemDirectory, entry.Name), overwrite: true); - } - } + InstallKeysFromZip(archive, installDirectory); } break; case ".keys": - File.Copy(keysSource, Path.Combine(systemDirectory, info.Name), true); + VerifyKeysFile(keysSource); + File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true); break; default: throw new InvalidFirmwarePackageException("Input file is not a valid key package"); } } + private void InstallKeysFromZip(ZipArchive archive, string installDirectory) + { + string temporaryDirectory = Path.Combine(installDirectory, "temp"); + if (Directory.Exists(temporaryDirectory)) + { + Directory.Delete(temporaryDirectory, true); + } + Directory.CreateDirectory(temporaryDirectory); + foreach (var entry in archive.Entries) + { + if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase)) + { + string extractDestination = Path.Combine(temporaryDirectory, entry.Name); + entry.ExtractToFile(extractDestination, overwrite: true); + try + { + VerifyKeysFile(extractDestination); + File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true); + } + catch (Exception) + { + Directory.Delete(temporaryDirectory, true); + throw; + } + } + } + Directory.Delete(temporaryDirectory, true); + } + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) { if (Directory.Exists(registeredDirectory)) @@ -999,20 +1017,41 @@ namespace Ryujinx.HLE.FileSystem return null; } - public bool AreKeysAlredyPresent() + public void VerifyKeysFile(string filePath) { - if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")) && !File.Exists(Path.Combine(AppDataManager.KeysDirPath, "title.keys"))) - { - if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && (File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")) || File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "title.keys")))) - { - return true; - } - } - else - { - return true; - } + string schemaPattern = @"^[a-zA-Z0-9_]+ = [a-zA-Z0-9]+$"; + if (File.Exists(filePath)) + { + // Read all lines from the file + string[] lines = File.ReadAllLines(filePath); + + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i].Trim(); + + // Check if the line matches the schema + if (!Regex.IsMatch(line, schemaPattern)) + { + throw new FormatException("Keys file doesn't have a correct schema."); + } + } + } else + { + throw new FileNotFoundException("Keys file not found at " + filePath); + } + } + + public bool AreKeysAlredyPresent(string pathToCheck) + { + string[] fileNames = { "prod.keys", "title.keys", "console.keys" }; + foreach (var file in fileNames) + { + if (File.Exists(Path.Combine(pathToCheck, file))) + { + return true; + } + } return false; } } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 53f865387..9ddc9f4c4 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -507,10 +507,10 @@ "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", - "DialogKeysInstallerKeysNotFoundErrorMessage": "A valid Keys file was not found in {0}.", + "DialogKeysInstallerKeysNotFoundErrorMessage": "An invalid Keys file was found in {0}", "DialogKeysInstallerKeysInstallTitle": "Install Keys", "DialogKeysInstallerKeysInstallMessage": "New Keys file will be installed.", - "DialogKeysInstallerKeysInstallSubMessage": "\n\nThis will replace some of the current installed Keys.", + "DialogKeysInstallerKeysInstallSubMessage": "\n\nThis may replace some of the current installed Keys.", "DialogKeysInstallerKeysInstallConfirmMessage": "\n\nDo you want to continue?", "DialogKeysInstallerKeysInstallWaitMessage": "Installing Keys...", "DialogKeysInstallerKeysInstallSuccessMessage": "New Keys file successfully installed.", diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 233e2d5bf..9ddfd746c 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1184,19 +1184,16 @@ namespace Ryujinx.Ava.UI.ViewModels { try { - //bool isValidKeysFilke = true; - - //if (!isValidKeysFilke) - //{ - // await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename)); - - // return; - //} + string systemDirectory = AppDataManager.KeysDirPath; + if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && Directory.Exists(AppDataManager.KeysDirPathUser)) + { + systemDirectory = AppDataManager.KeysDirPathUser; + } string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle); string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); - bool alreadyKesyInstalled = ContentManager.AreKeysAlredyPresent(); + bool alreadyKesyInstalled = ContentManager.AreKeysAlredyPresent(systemDirectory); if (alreadyKesyInstalled) { dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage); @@ -1226,7 +1223,7 @@ namespace Ryujinx.Ava.UI.ViewModels try { - ContentManager.InstallKeys(filename); + ContentManager.InstallKeys(filename, systemDirectory); Dispatcher.UIThread.InvokeAsync(async delegate { @@ -1250,7 +1247,13 @@ namespace Ryujinx.Ava.UI.ViewModels { waitingDialog.Close(); - await ContentDialogHelper.CreateErrorDialog(ex.Message); + string message = ex.Message; + if(ex is FormatException) + { + message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename); + } + + await ContentDialogHelper.CreateErrorDialog(message); }); } finally -- 2.47.1 From 8d8b0c07a330122ffaaa97fee9d57507086f3aed Mon Sep 17 00:00:00 2001 From: Nicola <61830443+nicola02nb@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:32:21 +0100 Subject: [PATCH 3/3] Fixed mime types button not updating afte clicked --- src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 10 ++++++++-- src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 9ddfd746c..51b8c65d7 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -102,6 +102,7 @@ namespace Ryujinx.Ava.UI.ViewModels private float _volumeBeforeMute; private string _backendText; + private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered; private bool _canUpdate = true; private Cursor _cursor; private string _title; @@ -804,10 +805,15 @@ namespace Ryujinx.Ava.UI.ViewModels { get => FileAssociationHelper.IsTypeAssociationSupported; } - + public bool AreMimeTypesRegistered { - get => FileAssociationHelper.AreMimeTypesRegistered; + get => _areMimeTypesRegistered; + set { + _areMimeTypesRegistered = value; + + OnPropertyChanged(); + } } public ObservableCollectionExtended Applications diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index ce4d9fd59..917246bac 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -165,7 +165,8 @@ namespace Ryujinx.Ava.UI.Views.Main private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) { - if (FileAssociationHelper.Install()) + ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install(); + if (ViewModel.AreMimeTypesRegistered) await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); else await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); @@ -173,7 +174,8 @@ namespace Ryujinx.Ava.UI.Views.Main private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) { - if (FileAssociationHelper.Uninstall()) + ViewModel.AreMimeTypesRegistered = !FileAssociationHelper.Uninstall(); + if (!ViewModel.AreMimeTypesRegistered) await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); else await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); -- 2.47.1