From f06d22d6f01e657ebbc0c8ef082739cd468e47b5 Mon Sep 17 00:00:00 2001
From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>
Date: Sun, 11 Feb 2024 02:09:18 +0000
Subject: Infra: Capitalisation Consistency (#6296)
* Rename Ryujinx.UI.Common
* Rename Ryujinx.UI.LocaleGenerator
* Update in Files
AboutWindow
* Configuration State
* Rename projects
* Ryujinx/UI
* Fix build
* Main remaining inconsistencies
* HLE.UI Namespace
* HLE.UI Files
* Namespace
* Ryujinx.UI.Common.Configuration.UI
* Ryujinx.UI.Common,Configuration.UI Files
* More instances
---
src/Ryujinx.Ava/App.axaml.cs | 16 +-
src/Ryujinx.Ava/AppHost.cs | 14 +-
src/Ryujinx.Ava/Common/ApplicationHelper.cs | 4 +-
src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs | 2 +-
src/Ryujinx.Ava/Common/Locale/LocaleManager.cs | 6 +-
src/Ryujinx.Ava/Modules/Updater/Updater.cs | 4 +-
src/Ryujinx.Ava/Program.cs | 8 +-
src/Ryujinx.Ava/Ryujinx.Ava.csproj | 6 +-
src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs | 204 ++
src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 204 --
.../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 2 +-
src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs | 41 +
src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs | 41 -
.../UI/Applet/ControllerAppletDialog.axaml.cs | 4 +-
src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 2 +-
src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 2 +-
.../UI/Applet/SwkbdAppletDialog.axaml.cs | 2 +-
.../UI/Controls/ApplicationContextMenu.axaml.cs | 4 +-
.../UI/Controls/ApplicationGridView.axaml.cs | 2 +-
.../UI/Controls/ApplicationListView.axaml.cs | 2 +-
src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml | 4 +-
.../UI/Helpers/ApplicationOpenedEventArgs.cs | 2 +-
src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs | 2 +-
.../UI/Helpers/LocalizedNeverConverter.cs | 2 +-
src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs | 4 +-
src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs | 4 +-
.../UI/Models/Generic/LastPlayedSortComparer.cs | 2 +-
.../UI/Models/Generic/TimePlayedSortComparer.cs | 2 +-
src/Ryujinx.Ava/UI/Models/SaveModel.cs | 4 +-
src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs | 4 +-
.../UI/Renderer/EmbeddedWindowOpenGL.cs | 4 +-
src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs | 2 +-
.../UI/ViewModels/AboutWindowViewModel.cs | 20 +-
.../UI/ViewModels/AmiiboWindowViewModel.cs | 4 +-
.../UI/ViewModels/ControllerInputViewModel.cs | 10 +-
.../UI/ViewModels/MainWindowViewModel.cs | 58 +-
src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs | 12 +-
.../UI/ViewModels/TitleUpdateViewModel.cs | 2 +-
.../UI/Views/Main/MainMenuBarView.axaml.cs | 8 +-
.../UI/Views/Main/MainStatusBarView.axaml.cs | 2 +-
.../UI/Views/Settings/SettingsHotkeysView.axaml | 2 +-
src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 2 +-
src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs | 2 +-
src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs | 2 +-
src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs | 2 +-
.../DownloadableContentManagerWindow.axaml.cs | 2 +-
src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs | 46 +-
.../UI/Windows/ModManagerWindow.axaml.cs | 2 +-
src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs | 4 +-
.../UI/Windows/TitleUpdateWindow.axaml.cs | 2 +-
.../Configuration/Hid/KeyboardHotkeys.cs | 2 +-
src/Ryujinx.Common/Logging/LogClass.cs | 2 +-
src/Ryujinx.HLE/HLEConfiguration.cs | 8 +-
.../HOS/Applets/Controller/ControllerApplet.cs | 4 +-
.../Applets/Controller/ControllerAppletUIArgs.cs | 14 +
.../Applets/Controller/ControllerAppletUiArgs.cs | 14 -
src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs | 8 +-
src/Ryujinx.HLE/HOS/Applets/IApplet.cs | 2 +-
.../SoftwareKeyboard/SoftwareKeyboardApplet.cs | 16 +-
.../SoftwareKeyboard/SoftwareKeyboardRenderer.cs | 12 +-
.../SoftwareKeyboardRendererBase.cs | 8 +-
.../SoftwareKeyboard/SoftwareKeyboardUIArgs.cs | 16 +
.../SoftwareKeyboard/SoftwareKeyboardUIState.cs | 22 +
.../SoftwareKeyboard/SoftwareKeyboardUiArgs.cs | 16 -
.../SoftwareKeyboard/SoftwareKeyboardUiState.cs | 22 -
.../ApplicationProxy/IApplicationFunctions.cs | 4 +-
.../Vi/RootService/IApplicationDisplayService.cs | 2 +-
src/Ryujinx.HLE/Switch.cs | 6 +-
src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs | 4 +
src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs | 16 +
src/Ryujinx.HLE/UI/IHostUIHandler.cs | 51 +
src/Ryujinx.HLE/UI/IHostUITheme.cs | 13 +
src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs | 6 +
src/Ryujinx.HLE/UI/Input/NpadReader.cs | 140 +
src/Ryujinx.HLE/UI/KeyPressedHandler.cs | 6 +
src/Ryujinx.HLE/UI/KeyReleasedHandler.cs | 6 +
src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs | 45 +
src/Ryujinx.HLE/UI/ThemeColor.cs | 18 +
src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs | 4 -
src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs | 16 -
src/Ryujinx.HLE/Ui/IHostUiHandler.cs | 51 -
src/Ryujinx.HLE/Ui/IHostUiTheme.cs | 13 -
src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs | 6 -
src/Ryujinx.HLE/Ui/Input/NpadReader.cs | 140 -
src/Ryujinx.HLE/Ui/KeyPressedHandler.cs | 6 -
src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs | 6 -
src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs | 45 -
src/Ryujinx.HLE/Ui/ThemeColor.cs | 18 -
.../HeadlessDynamicTextInputHandler.cs | 2 +-
src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs | 4 +-
src/Ryujinx.Headless.SDL2/WindowBase.cs | 12 +-
.../App/ApplicationAddedEventArgs.cs | 9 +
.../App/ApplicationCountUpdatedEventArgs.cs | 10 +
src/Ryujinx.UI.Common/App/ApplicationData.cs | 158 +
.../App/ApplicationJsonSerializerContext.cs | 10 +
src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 930 ++++++
src/Ryujinx.UI.Common/App/ApplicationMetadata.cs | 51 +
.../Configuration/AudioBackend.cs | 14 +
.../Configuration/ConfigurationFileFormat.cs | 409 +++
.../ConfigurationFileFormatSettings.cs | 9 +
.../ConfigurationJsonSerializerContext.cs | 10 +
.../Configuration/ConfigurationState.cs | 1562 ++++++++++
src/Ryujinx.UI.Common/Configuration/FileTypes.cs | 12 +
.../Configuration/LoggerModule.cs | 113 +
.../Configuration/System/Language.cs | 28 +
.../Configuration/System/Region.cs | 17 +
.../Configuration/UI/ColumnSort.cs | 8 +
.../Configuration/UI/GuiColumns.cs | 16 +
.../Configuration/UI/ShownFileTypes.cs | 12 +
.../Configuration/UI/WindowStartup.cs | 11 +
src/Ryujinx.UI.Common/DiscordIntegrationModule.cs | 98 +
.../Extensions/FileTypeExtensions.cs | 25 +
src/Ryujinx.UI.Common/Helper/CommandLineState.cs | 99 +
src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs | 50 +
.../Helper/FileAssociationHelper.cs | 202 ++
src/Ryujinx.UI.Common/Helper/LinuxHelper.cs | 62 +
src/Ryujinx.UI.Common/Helper/ObjectiveC.cs | 160 +
src/Ryujinx.UI.Common/Helper/OpenHelper.cs | 112 +
src/Ryujinx.UI.Common/Helper/SetupValidator.cs | 114 +
src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs | 162 +
src/Ryujinx.UI.Common/Helper/TitleHelper.cs | 30 +
src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs | 219 ++
src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs | 67 +
.../Models/Amiibo/AmiiboApiGamesSwitch.cs | 15 +
.../Models/Amiibo/AmiiboApiUsage.cs | 12 +
src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs | 14 +
.../Models/Amiibo/AmiiboJsonSerializerContext.cs | 9 +
.../Github/GithubReleaseAssetJsonResponse.cs | 9 +
.../Models/Github/GithubReleasesJsonResponse.cs | 10 +
.../Github/GithubReleasesJsonSerializerContext.cs | 9 +
.../Resources/Controller_JoyConLeft.svg | 1 +
.../Resources/Controller_JoyConPair.svg | 1 +
.../Resources/Controller_JoyConRight.svg | 1 +
.../Resources/Controller_ProCon.svg | 132 +
src/Ryujinx.UI.Common/Resources/Icon_NCA.png | Bin 0 -> 10190 bytes
src/Ryujinx.UI.Common/Resources/Icon_NRO.png | Bin 0 -> 10254 bytes
src/Ryujinx.UI.Common/Resources/Icon_NSO.png | Bin 0 -> 10354 bytes
src/Ryujinx.UI.Common/Resources/Icon_NSP.png | Bin 0 -> 9899 bytes
src/Ryujinx.UI.Common/Resources/Icon_XCI.png | Bin 0 -> 9809 bytes
src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png | Bin 0 -> 10573 bytes
.../Resources/Logo_Discord_Dark.png | Bin 0 -> 9835 bytes
.../Resources/Logo_Discord_Light.png | Bin 0 -> 10765 bytes
.../Resources/Logo_GitHub_Dark.png | Bin 0 -> 4837 bytes
.../Resources/Logo_GitHub_Light.png | Bin 0 -> 5166 bytes
.../Resources/Logo_Patreon_Dark.png | Bin 0 -> 52210 bytes
.../Resources/Logo_Patreon_Light.png | Bin 0 -> 29395 bytes
src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes
.../Resources/Logo_Twitter_Dark.png | Bin 0 -> 18385 bytes
.../Resources/Logo_Twitter_Light.png | Bin 0 -> 19901 bytes
src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj | 68 +
.../SystemInfo/LinuxSystemInfo.cs | 85 +
.../SystemInfo/MacOSSystemInfo.cs | 164 +
src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs | 79 +
.../SystemInfo/WindowsSystemInfo.cs | 87 +
src/Ryujinx.UI.Common/UserError.cs | 39 +
src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs | 33 +
.../Ryujinx.UI.LocaleGenerator.csproj | 18 +
.../App/ApplicationAddedEventArgs.cs | 9 -
.../App/ApplicationCountUpdatedEventArgs.cs | 10 -
src/Ryujinx.Ui.Common/App/ApplicationData.cs | 158 -
.../App/ApplicationJsonSerializerContext.cs | 10 -
src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs | 930 ------
src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs | 51 -
.../Configuration/AudioBackend.cs | 14 -
.../Configuration/ConfigurationFileFormat.cs | 409 ---
.../ConfigurationFileFormatSettings.cs | 9 -
.../ConfigurationJsonSerializerContext.cs | 10 -
.../Configuration/ConfigurationState.cs | 1562 ----------
src/Ryujinx.Ui.Common/Configuration/FileTypes.cs | 12 -
.../Configuration/LoggerModule.cs | 113 -
.../Configuration/System/Language.cs | 28 -
.../Configuration/System/Region.cs | 17 -
.../Configuration/Ui/ColumnSort.cs | 8 -
.../Configuration/Ui/GuiColumns.cs | 16 -
.../Configuration/Ui/ShownFileTypes.cs | 12 -
.../Configuration/Ui/WindowStartup.cs | 11 -
src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs | 98 -
.../Extensions/FileTypeExtensions.cs | 25 -
src/Ryujinx.Ui.Common/Helper/CommandLineState.cs | 99 -
src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs | 50 -
.../Helper/FileAssociationHelper.cs | 202 --
src/Ryujinx.Ui.Common/Helper/LinuxHelper.cs | 62 -
src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs | 160 -
src/Ryujinx.Ui.Common/Helper/OpenHelper.cs | 112 -
src/Ryujinx.Ui.Common/Helper/SetupValidator.cs | 114 -
src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs | 162 -
src/Ryujinx.Ui.Common/Helper/TitleHelper.cs | 30 -
src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs | 219 --
src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs | 67 -
.../Models/Amiibo/AmiiboApiGamesSwitch.cs | 15 -
.../Models/Amiibo/AmiiboApiUsage.cs | 12 -
src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs | 14 -
.../Models/Amiibo/AmiiboJsonSerializerContext.cs | 9 -
.../Github/GithubReleaseAssetJsonResponse.cs | 9 -
.../Models/Github/GithubReleasesJsonResponse.cs | 10 -
.../Github/GithubReleasesJsonSerializerContext.cs | 9 -
.../Resources/Controller_JoyConLeft.svg | 1 -
.../Resources/Controller_JoyConPair.svg | 1 -
.../Resources/Controller_JoyConRight.svg | 1 -
.../Resources/Controller_ProCon.svg | 132 -
src/Ryujinx.Ui.Common/Resources/Icon_NCA.png | Bin 10190 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Icon_NRO.png | Bin 10254 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Icon_NSO.png | Bin 10354 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Icon_NSP.png | Bin 9899 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Icon_XCI.png | Bin 9809 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png | Bin 10573 -> 0 bytes
.../Resources/Logo_Discord_Dark.png | Bin 9835 -> 0 bytes
.../Resources/Logo_Discord_Light.png | Bin 10765 -> 0 bytes
.../Resources/Logo_GitHub_Dark.png | Bin 4837 -> 0 bytes
.../Resources/Logo_GitHub_Light.png | Bin 5166 -> 0 bytes
.../Resources/Logo_Patreon_Dark.png | Bin 52210 -> 0 bytes
.../Resources/Logo_Patreon_Light.png | Bin 29395 -> 0 bytes
src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png | Bin 52972 -> 0 bytes
.../Resources/Logo_Twitter_Dark.png | Bin 18385 -> 0 bytes
.../Resources/Logo_Twitter_Light.png | Bin 19901 -> 0 bytes
src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj | 68 -
.../SystemInfo/LinuxSystemInfo.cs | 85 -
.../SystemInfo/MacOSSystemInfo.cs | 164 -
src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs | 79 -
.../SystemInfo/WindowsSystemInfo.cs | 87 -
src/Ryujinx.Ui.Common/UserError.cs | 39 -
src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs | 33 -
.../Ryujinx.Ui.LocaleGenerator.csproj | 18 -
src/Ryujinx/Modules/Updater/UpdateDialog.cs | 8 +-
src/Ryujinx/Modules/Updater/Updater.cs | 6 +-
src/Ryujinx/Program.cs | 12 +-
src/Ryujinx/Ryujinx.csproj | 30 +-
src/Ryujinx/UI/Applet/ErrorAppletDialog.cs | 31 +
.../UI/Applet/GtkDynamicTextInputHandler.cs | 108 +
src/Ryujinx/UI/Applet/GtkHostUIHandler.cs | 200 ++
src/Ryujinx/UI/Applet/GtkHostUITheme.cs | 90 +
src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs | 127 +
src/Ryujinx/UI/Helper/MetalHelper.cs | 135 +
src/Ryujinx/UI/Helper/SortHelper.cs | 33 +
src/Ryujinx/UI/Helper/ThemeHelper.cs | 36 +
src/Ryujinx/UI/MainWindow.cs | 1941 ++++++++++++
src/Ryujinx/UI/MainWindow.glade | 1006 ++++++
src/Ryujinx/UI/OpenGLRenderer.cs | 142 +
src/Ryujinx/UI/OpenToolkitBindingsContext.cs | 20 +
src/Ryujinx/UI/RendererWidgetBase.cs | 803 +++++
src/Ryujinx/UI/SPBOpenGLContext.cs | 49 +
src/Ryujinx/UI/StatusUpdatedEventArgs.cs | 28 +
src/Ryujinx/UI/VulkanRenderer.cs | 93 +
.../UI/Widgets/GameTableContextMenu.Designer.cs | 233 ++
src/Ryujinx/UI/Widgets/GameTableContextMenu.cs | 644 ++++
src/Ryujinx/UI/Widgets/GtkDialog.cs | 114 +
src/Ryujinx/UI/Widgets/GtkInputDialog.cs | 37 +
src/Ryujinx/UI/Widgets/ProfileDialog.cs | 57 +
src/Ryujinx/UI/Widgets/ProfileDialog.glade | 124 +
src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs | 27 +
src/Ryujinx/UI/Widgets/UserErrorDialog.cs | 123 +
src/Ryujinx/UI/Windows/AboutWindow.Designer.cs | 511 ++++
src/Ryujinx/UI/Windows/AboutWindow.cs | 85 +
src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs | 190 ++
src/Ryujinx/UI/Windows/AmiiboWindow.cs | 438 +++
src/Ryujinx/UI/Windows/AvatarWindow.cs | 291 ++
src/Ryujinx/UI/Windows/CheatWindow.cs | 156 +
src/Ryujinx/UI/Windows/CheatWindow.glade | 150 +
src/Ryujinx/UI/Windows/ControllerWindow.cs | 1230 ++++++++
src/Ryujinx/UI/Windows/ControllerWindow.glade | 2241 ++++++++++++++
src/Ryujinx/UI/Windows/DlcWindow.cs | 280 ++
src/Ryujinx/UI/Windows/DlcWindow.glade | 202 ++
src/Ryujinx/UI/Windows/SettingsWindow.cs | 847 +++++
src/Ryujinx/UI/Windows/SettingsWindow.glade | 3221 ++++++++++++++++++++
src/Ryujinx/UI/Windows/TitleUpdateWindow.cs | 206 ++
src/Ryujinx/UI/Windows/TitleUpdateWindow.glade | 214 ++
.../Windows/UserProfilesManagerWindow.Designer.cs | 255 ++
.../UI/Windows/UserProfilesManagerWindow.cs | 328 ++
src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs | 31 -
.../Ui/Applet/GtkDynamicTextInputHandler.cs | 108 -
src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs | 200 --
src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs | 90 -
src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs | 127 -
src/Ryujinx/Ui/Helper/MetalHelper.cs | 135 -
src/Ryujinx/Ui/Helper/SortHelper.cs | 33 -
src/Ryujinx/Ui/Helper/ThemeHelper.cs | 36 -
src/Ryujinx/Ui/MainWindow.cs | 1941 ------------
src/Ryujinx/Ui/MainWindow.glade | 1006 ------
src/Ryujinx/Ui/OpenGLRenderer.cs | 142 -
src/Ryujinx/Ui/OpenToolkitBindingsContext.cs | 20 -
src/Ryujinx/Ui/RendererWidgetBase.cs | 803 -----
src/Ryujinx/Ui/SPBOpenGLContext.cs | 49 -
src/Ryujinx/Ui/StatusUpdatedEventArgs.cs | 28 -
src/Ryujinx/Ui/VulkanRenderer.cs | 93 -
.../Ui/Widgets/GameTableContextMenu.Designer.cs | 233 --
src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 644 ----
src/Ryujinx/Ui/Widgets/GtkDialog.cs | 114 -
src/Ryujinx/Ui/Widgets/GtkInputDialog.cs | 37 -
src/Ryujinx/Ui/Widgets/ProfileDialog.cs | 57 -
src/Ryujinx/Ui/Widgets/ProfileDialog.glade | 124 -
src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs | 27 -
src/Ryujinx/Ui/Widgets/UserErrorDialog.cs | 123 -
src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs | 511 ----
src/Ryujinx/Ui/Windows/AboutWindow.cs | 85 -
src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs | 190 --
src/Ryujinx/Ui/Windows/AmiiboWindow.cs | 438 ---
src/Ryujinx/Ui/Windows/AvatarWindow.cs | 291 --
src/Ryujinx/Ui/Windows/CheatWindow.cs | 156 -
src/Ryujinx/Ui/Windows/CheatWindow.glade | 150 -
src/Ryujinx/Ui/Windows/ControllerWindow.cs | 1230 --------
src/Ryujinx/Ui/Windows/ControllerWindow.glade | 2241 --------------
src/Ryujinx/Ui/Windows/DlcWindow.cs | 280 --
src/Ryujinx/Ui/Windows/DlcWindow.glade | 202 --
src/Ryujinx/Ui/Windows/SettingsWindow.cs | 847 -----
src/Ryujinx/Ui/Windows/SettingsWindow.glade | 3221 --------------------
src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs | 206 --
src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade | 214 --
.../Windows/UserProfilesManagerWindow.Designer.cs | 255 --
.../Ui/Windows/UserProfilesManagerWindow.cs | 328 --
309 files changed, 23446 insertions(+), 23446 deletions(-)
create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs
delete mode 100644 src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs
delete mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs
create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs
delete mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs
create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs
delete mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
delete mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs
create mode 100644 src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/IHostUIHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/IHostUITheme.cs
create mode 100644 src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/Input/NpadReader.cs
create mode 100644 src/Ryujinx.HLE/UI/KeyPressedHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/KeyReleasedHandler.cs
create mode 100644 src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs
create mode 100644 src/Ryujinx.HLE/UI/ThemeColor.cs
delete mode 100644 src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/IHostUiHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/IHostUiTheme.cs
delete mode 100644 src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/Input/NpadReader.cs
delete mode 100644 src/Ryujinx.HLE/Ui/KeyPressedHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs
delete mode 100644 src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs
delete mode 100644 src/Ryujinx.HLE/Ui/ThemeColor.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationData.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
create mode 100644 src/Ryujinx.UI.Common/App/ApplicationMetadata.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/AudioBackend.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/FileTypes.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/LoggerModule.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/System/Language.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/System/Region.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs
create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs
create mode 100644 src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
create mode 100644 src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/CommandLineState.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/LinuxHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/ObjectiveC.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/OpenHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/SetupValidator.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/TitleHelper.cs
create mode 100644 src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs
create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs
create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg
create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg
create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg
create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg
create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NCA.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NRO.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NSO.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NSP.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_XCI.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Discord_Light.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Twitter_Dark.png
create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png
create mode 100644 src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj
create mode 100644 src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs
create mode 100644 src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs
create mode 100644 src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs
create mode 100644 src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs
create mode 100644 src/Ryujinx.UI.Common/UserError.cs
create mode 100644 src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs
create mode 100644 src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationAddedEventArgs.cs
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationCountUpdatedEventArgs.cs
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationData.cs
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
delete mode 100644 src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/AudioBackend.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/FileTypes.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/System/Language.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/System/Region.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/ColumnSort.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/GuiColumns.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/ShownFileTypes.cs
delete mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs
delete mode 100644 src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs
delete mode 100644 src/Ryujinx.Ui.Common/Extensions/FileTypeExtensions.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/CommandLineState.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/LinuxHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/OpenHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/SetupValidator.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/TitleHelper.cs
delete mode 100644 src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs
delete mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_ProCon.svg
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NCA.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NRO.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NSO.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NSP.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_XCI.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Discord_Dark.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Discord_Light.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Dark.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Light.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Dark.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Light.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Dark.png
delete mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Light.png
delete mode 100644 src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj
delete mode 100644 src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs
delete mode 100644 src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs
delete mode 100644 src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs
delete mode 100644 src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs
delete mode 100644 src/Ryujinx.Ui.Common/UserError.cs
delete mode 100644 src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs
delete mode 100644 src/Ryujinx.Ui.LocaleGenerator/Ryujinx.Ui.LocaleGenerator.csproj
create mode 100644 src/Ryujinx/UI/Applet/ErrorAppletDialog.cs
create mode 100644 src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs
create mode 100644 src/Ryujinx/UI/Applet/GtkHostUIHandler.cs
create mode 100644 src/Ryujinx/UI/Applet/GtkHostUITheme.cs
create mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs
create mode 100644 src/Ryujinx/UI/Helper/MetalHelper.cs
create mode 100644 src/Ryujinx/UI/Helper/SortHelper.cs
create mode 100644 src/Ryujinx/UI/Helper/ThemeHelper.cs
create mode 100644 src/Ryujinx/UI/MainWindow.cs
create mode 100644 src/Ryujinx/UI/MainWindow.glade
create mode 100644 src/Ryujinx/UI/OpenGLRenderer.cs
create mode 100644 src/Ryujinx/UI/OpenToolkitBindingsContext.cs
create mode 100644 src/Ryujinx/UI/RendererWidgetBase.cs
create mode 100644 src/Ryujinx/UI/SPBOpenGLContext.cs
create mode 100644 src/Ryujinx/UI/StatusUpdatedEventArgs.cs
create mode 100644 src/Ryujinx/UI/VulkanRenderer.cs
create mode 100644 src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs
create mode 100644 src/Ryujinx/UI/Widgets/GameTableContextMenu.cs
create mode 100644 src/Ryujinx/UI/Widgets/GtkDialog.cs
create mode 100644 src/Ryujinx/UI/Widgets/GtkInputDialog.cs
create mode 100644 src/Ryujinx/UI/Widgets/ProfileDialog.cs
create mode 100644 src/Ryujinx/UI/Widgets/ProfileDialog.glade
create mode 100644 src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs
create mode 100644 src/Ryujinx/UI/Widgets/UserErrorDialog.cs
create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.Designer.cs
create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs
create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/AvatarWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.glade
create mode 100644 src/Ryujinx/UI/Windows/ControllerWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/ControllerWindow.glade
create mode 100644 src/Ryujinx/UI/Windows/DlcWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/DlcWindow.glade
create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.glade
create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.cs
create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.glade
create mode 100644 src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs
create mode 100644 src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs
delete mode 100644 src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs
delete mode 100644 src/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs
delete mode 100644 src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
delete mode 100644 src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs
delete mode 100644 src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
delete mode 100644 src/Ryujinx/Ui/Helper/MetalHelper.cs
delete mode 100644 src/Ryujinx/Ui/Helper/SortHelper.cs
delete mode 100644 src/Ryujinx/Ui/Helper/ThemeHelper.cs
delete mode 100644 src/Ryujinx/Ui/MainWindow.cs
delete mode 100644 src/Ryujinx/Ui/MainWindow.glade
delete mode 100644 src/Ryujinx/Ui/OpenGLRenderer.cs
delete mode 100644 src/Ryujinx/Ui/OpenToolkitBindingsContext.cs
delete mode 100644 src/Ryujinx/Ui/RendererWidgetBase.cs
delete mode 100644 src/Ryujinx/Ui/SPBOpenGLContext.cs
delete mode 100644 src/Ryujinx/Ui/StatusUpdatedEventArgs.cs
delete mode 100644 src/Ryujinx/Ui/VulkanRenderer.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/GtkDialog.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/GtkInputDialog.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/ProfileDialog.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/ProfileDialog.glade
delete mode 100644 src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs
delete mode 100644 src/Ryujinx/Ui/Widgets/UserErrorDialog.cs
delete mode 100644 src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
delete mode 100644 src/Ryujinx/Ui/Windows/AboutWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
delete mode 100644 src/Ryujinx/Ui/Windows/AmiiboWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/AvatarWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/CheatWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/CheatWindow.glade
delete mode 100644 src/Ryujinx/Ui/Windows/ControllerWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/ControllerWindow.glade
delete mode 100644 src/Ryujinx/Ui/Windows/DlcWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/DlcWindow.glade
delete mode 100644 src/Ryujinx/Ui/Windows/SettingsWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/SettingsWindow.glade
delete mode 100644 src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
delete mode 100644 src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade
delete mode 100644 src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs
delete mode 100644 src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs
(limited to 'src')
diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs
index 54e61c67..387a6dc1 100644
--- a/src/Ryujinx.Ava/App.axaml.cs
+++ b/src/Ryujinx.Ava/App.axaml.cs
@@ -8,8 +8,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
using System;
using System.Diagnostics;
@@ -42,9 +42,9 @@ namespace Ryujinx.Ava
{
ApplyConfiguredTheme();
- ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event;
- ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event;
- ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event;
+ ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event;
+ ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event;
+ ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event;
}
}
@@ -88,13 +88,13 @@ namespace Ryujinx.Ava
{
try
{
- string baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
+ string baseStyle = ConfigurationState.Instance.UI.BaseStyle;
if (string.IsNullOrWhiteSpace(baseStyle))
{
- ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark";
+ ConfigurationState.Instance.UI.BaseStyle.Value = "Dark";
- baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
+ baseStyle = ConfigurationState.Instance.UI.BaseStyle;
}
RequestedThemeVariant = baseStyle switch
diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs
index 696a4046..04cec957 100644
--- a/src/Ryujinx.Ava/AppHost.cs
+++ b/src/Ryujinx.Ava/AppHost.cs
@@ -34,10 +34,10 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
-using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
using Silk.NET.Vulkan;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
@@ -1070,7 +1070,7 @@ namespace Ryujinx.Ava
case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true;
break;
- case KeyboardHotkeyState.ShowUi:
+ case KeyboardHotkeyState.ShowUI:
_viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar;
break;
case KeyboardHotkeyState.Pause:
@@ -1160,9 +1160,9 @@ namespace Ryujinx.Ava
{
state = KeyboardHotkeyState.Screenshot;
}
- else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi))
+ else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI))
{
- state = KeyboardHotkeyState.ShowUi;
+ state = KeyboardHotkeyState.ShowUI;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause))
{
diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx.Ava/Common/ApplicationHelper.cs
index 91ca8f4d..622a6a02 100644
--- a/src/Ryujinx.Ava/Common/ApplicationHelper.cs
+++ b/src/Ryujinx.Ava/Common/ApplicationHelper.cs
@@ -18,8 +18,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common.Helper;
using System;
using System.Buffers;
using System.IO;
diff --git a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs b/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs
index b2401640..6e492098 100644
--- a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs
+++ b/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs
@@ -5,7 +5,7 @@ namespace Ryujinx.Ava.Common
None,
ToggleVSync,
Screenshot,
- ShowUi,
+ ShowUI,
Pause,
ToggleMute,
ResScaleUp,
diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs
index 0e613838..b2f3e7ab 100644
--- a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs
+++ b/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs
@@ -1,7 +1,7 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -38,9 +38,9 @@ namespace Ryujinx.Ava.Common.Locale
// If the view is loaded with the UI Previewer detached, then override it with the saved one or default.
if (Program.PreviewerDetached)
{
- if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value))
+ if (!string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value))
{
- localeLanguageCode = ConfigurationState.Instance.Ui.LanguageCode.Value;
+ localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value;
}
else
{
diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs
index ad33b101..bd211fa5 100644
--- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs
+++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs
@@ -10,8 +10,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
-using Ryujinx.Ui.Common.Helper;
-using Ryujinx.Ui.Common.Models.Github;
+using Ryujinx.UI.Common.Helper;
+using Ryujinx.UI.Common.Models.Github;
using System;
using System.Collections.Generic;
using System.Diagnostics;
diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs
index d85749ef..7751bb52 100644
--- a/src/Ryujinx.Ava/Program.cs
+++ b/src/Ryujinx.Ava/Program.cs
@@ -9,10 +9,10 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
-using Ryujinx.Ui.Common;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
-using Ryujinx.Ui.Common.SystemInfo;
+using Ryujinx.UI.Common;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
+using Ryujinx.UI.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;
diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj
index 5665178a..91c2744f 100644
--- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj
+++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj
@@ -73,8 +73,8 @@
-
-
+
+
@@ -103,7 +103,7 @@
-
+
Designer
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs
new file mode 100644
index 00000000..4bcc35a7
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs
@@ -0,0 +1,204 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.Windows;
+using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+using Ryujinx.HLE.UI;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Ava.UI.Applet
+{
+ internal class AvaHostUIHandler : IHostUIHandler
+ {
+ private readonly MainWindow _parent;
+
+ public IHostUITheme HostUITheme { get; }
+
+ public AvaHostUIHandler(MainWindow parent)
+ {
+ _parent = parent;
+
+ HostUITheme = new AvaloniaHostUITheme(parent);
+ }
+
+ public bool DisplayMessageDialog(ControllerAppletUIArgs args)
+ {
+ ManualResetEvent dialogCloseEvent = new(false);
+
+ bool okPressed = false;
+
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ var response = await ControllerAppletDialog.ShowControllerAppletDialog(_parent, args);
+ if (response == UserResult.Ok)
+ {
+ okPressed = true;
+ }
+
+ dialogCloseEvent.Set();
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ return okPressed;
+ }
+
+ public bool DisplayMessageDialog(string title, string message)
+ {
+ ManualResetEvent dialogCloseEvent = new(false);
+
+ bool okPressed = false;
+
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ try
+ {
+ ManualResetEvent deferEvent = new(false);
+
+ bool opened = false;
+
+ UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
+ title,
+ message,
+ "",
+ LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
+ "",
+ LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
+ (int)Symbol.Important,
+ deferEvent,
+ async window =>
+ {
+ if (opened)
+ {
+ return;
+ }
+
+ opened = true;
+
+ _parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
+
+ await _parent.SettingsWindow.ShowDialog(window);
+
+ _parent.SettingsWindow = null;
+
+ opened = false;
+ });
+
+ if (response == UserResult.Ok)
+ {
+ okPressed = true;
+ }
+
+ dialogCloseEvent.Set();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
+
+ dialogCloseEvent.Set();
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ return okPressed;
+ }
+
+ public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
+ {
+ ManualResetEvent dialogCloseEvent = new(false);
+
+ bool okPressed = false;
+ bool error = false;
+ string inputText = args.InitialText ?? "";
+
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ try
+ {
+ var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
+
+ if (response.Result == UserResult.Ok)
+ {
+ inputText = response.Input;
+ okPressed = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ error = true;
+
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
+ }
+ finally
+ {
+ dialogCloseEvent.Set();
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ userText = error ? null : inputText;
+
+ return error || okPressed;
+ }
+
+ public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
+ {
+ device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
+ _parent.ViewModel.AppHost?.Stop();
+ }
+
+ public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
+ {
+ ManualResetEvent dialogCloseEvent = new(false);
+
+ bool showDetails = false;
+
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ try
+ {
+ ErrorAppletWindow msgDialog = new(_parent, buttons, message)
+ {
+ Title = title,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
+ Width = 400,
+ };
+
+ object response = await msgDialog.Run();
+
+ if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
+ {
+ showDetails = true;
+ }
+
+ dialogCloseEvent.Set();
+
+ msgDialog.Close();
+ }
+ catch (Exception ex)
+ {
+ dialogCloseEvent.Set();
+
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ return showDetails;
+ }
+
+ public IDynamicTextInputHandler CreateDynamicTextInputHandler()
+ {
+ return new AvaloniaDynamicTextInputHandler(_parent);
+ }
+ }
+}
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
deleted file mode 100644
index e1193910..00000000
--- a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Threading;
-using FluentAvalonia.UI.Controls;
-using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ava.UI.Controls;
-using Ryujinx.Ava.UI.Helpers;
-using Ryujinx.Ava.UI.Windows;
-using Ryujinx.HLE;
-using Ryujinx.HLE.HOS.Applets;
-using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
-using Ryujinx.HLE.Ui;
-using System;
-using System.Threading;
-
-namespace Ryujinx.Ava.UI.Applet
-{
- internal class AvaHostUiHandler : IHostUiHandler
- {
- private readonly MainWindow _parent;
-
- public IHostUiTheme HostUiTheme { get; }
-
- public AvaHostUiHandler(MainWindow parent)
- {
- _parent = parent;
-
- HostUiTheme = new AvaloniaHostUiTheme(parent);
- }
-
- public bool DisplayMessageDialog(ControllerAppletUiArgs args)
- {
- ManualResetEvent dialogCloseEvent = new(false);
-
- bool okPressed = false;
-
- Dispatcher.UIThread.InvokeAsync(async () =>
- {
- var response = await ControllerAppletDialog.ShowControllerAppletDialog(_parent, args);
- if (response == UserResult.Ok)
- {
- okPressed = true;
- }
-
- dialogCloseEvent.Set();
- });
-
- dialogCloseEvent.WaitOne();
-
- return okPressed;
- }
-
- public bool DisplayMessageDialog(string title, string message)
- {
- ManualResetEvent dialogCloseEvent = new(false);
-
- bool okPressed = false;
-
- Dispatcher.UIThread.InvokeAsync(async () =>
- {
- try
- {
- ManualResetEvent deferEvent = new(false);
-
- bool opened = false;
-
- UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
- title,
- message,
- "",
- LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
- "",
- LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
- (int)Symbol.Important,
- deferEvent,
- async window =>
- {
- if (opened)
- {
- return;
- }
-
- opened = true;
-
- _parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
-
- await _parent.SettingsWindow.ShowDialog(window);
-
- _parent.SettingsWindow = null;
-
- opened = false;
- });
-
- if (response == UserResult.Ok)
- {
- okPressed = true;
- }
-
- dialogCloseEvent.Set();
- }
- catch (Exception ex)
- {
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
-
- dialogCloseEvent.Set();
- }
- });
-
- dialogCloseEvent.WaitOne();
-
- return okPressed;
- }
-
- public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
- {
- ManualResetEvent dialogCloseEvent = new(false);
-
- bool okPressed = false;
- bool error = false;
- string inputText = args.InitialText ?? "";
-
- Dispatcher.UIThread.InvokeAsync(async () =>
- {
- try
- {
- var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
-
- if (response.Result == UserResult.Ok)
- {
- inputText = response.Input;
- okPressed = true;
- }
- }
- catch (Exception ex)
- {
- error = true;
-
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
- }
- finally
- {
- dialogCloseEvent.Set();
- }
- });
-
- dialogCloseEvent.WaitOne();
-
- userText = error ? null : inputText;
-
- return error || okPressed;
- }
-
- public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
- {
- device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
- _parent.ViewModel.AppHost?.Stop();
- }
-
- public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
- {
- ManualResetEvent dialogCloseEvent = new(false);
-
- bool showDetails = false;
-
- Dispatcher.UIThread.InvokeAsync(async () =>
- {
- try
- {
- ErrorAppletWindow msgDialog = new(_parent, buttons, message)
- {
- Title = title,
- WindowStartupLocation = WindowStartupLocation.CenterScreen,
- Width = 400,
- };
-
- object response = await msgDialog.Run();
-
- if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
- {
- showDetails = true;
- }
-
- dialogCloseEvent.Set();
-
- msgDialog.Close();
- }
- catch (Exception ex)
- {
- dialogCloseEvent.Set();
-
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
- }
- });
-
- dialogCloseEvent.WaitOne();
-
- return showDetails;
- }
-
- public IDynamicTextInputHandler CreateDynamicTextInputHandler()
- {
- return new AvaloniaDynamicTextInputHandler(_parent);
- }
- }
-}
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs
index 2411659f..531d0061 100644
--- a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs
+++ b/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs
@@ -5,7 +5,7 @@ using Avalonia.Threading;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using System;
using System.Threading;
using HidKey = Ryujinx.Common.Configuration.Hid.Key;
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs
new file mode 100644
index 00000000..016fb484
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs
@@ -0,0 +1,41 @@
+using Avalonia.Media;
+using Ryujinx.Ava.UI.Windows;
+using Ryujinx.HLE.UI;
+using System;
+
+namespace Ryujinx.Ava.UI.Applet
+{
+ class AvaloniaHostUITheme : IHostUITheme
+ {
+ public AvaloniaHostUITheme(MainWindow parent)
+ {
+ FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
+ DefaultBackgroundColor = BrushToThemeColor(parent.Background);
+ DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
+ DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
+ SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
+ SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
+ }
+
+ public string FontFamily { get; }
+
+ public ThemeColor DefaultBackgroundColor { get; }
+ public ThemeColor DefaultForegroundColor { get; }
+ public ThemeColor DefaultBorderColor { get; }
+ public ThemeColor SelectionBackgroundColor { get; }
+ public ThemeColor SelectionForegroundColor { get; }
+
+ private static ThemeColor BrushToThemeColor(IBrush brush)
+ {
+ if (brush is SolidColorBrush solidColor)
+ {
+ return new ThemeColor((float)solidColor.Color.A / 255,
+ (float)solidColor.Color.R / 255,
+ (float)solidColor.Color.G / 255,
+ (float)solidColor.Color.B / 255);
+ }
+
+ return new ThemeColor();
+ }
+ }
+}
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs
deleted file mode 100644
index 4ee177d7..00000000
--- a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Avalonia.Media;
-using Ryujinx.Ava.UI.Windows;
-using Ryujinx.HLE.Ui;
-using System;
-
-namespace Ryujinx.Ava.UI.Applet
-{
- class AvaloniaHostUiTheme : IHostUiTheme
- {
- public AvaloniaHostUiTheme(MainWindow parent)
- {
- FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
- DefaultBackgroundColor = BrushToThemeColor(parent.Background);
- DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
- DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
- SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
- SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
- }
-
- public string FontFamily { get; }
-
- public ThemeColor DefaultBackgroundColor { get; }
- public ThemeColor DefaultForegroundColor { get; }
- public ThemeColor DefaultBorderColor { get; }
- public ThemeColor SelectionBackgroundColor { get; }
- public ThemeColor SelectionForegroundColor { get; }
-
- private static ThemeColor BrushToThemeColor(IBrush brush)
- {
- if (brush is SolidColorBrush solidColor)
- {
- return new ThemeColor((float)solidColor.Color.A / 255,
- (float)solidColor.Color.R / 255,
- (float)solidColor.Color.G / 255,
- (float)solidColor.Color.B / 255);
- }
-
- return new ThemeColor();
- }
- }
-}
diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs
index f7d751a6..279af07c 100644
--- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs
@@ -36,7 +36,7 @@ namespace Ryujinx.Ava.UI.Applet
private readonly MainWindow _mainWindow;
- public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUiArgs args)
+ public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args)
{
if (args.PlayerCountMin == args.PlayerCountMax)
{
@@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Applet
InitializeComponent();
}
- public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUiArgs args)
+ public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args)
{
ContentDialog contentDialog = new();
UserResult result = UserResult.Cancel;
diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
index 6186b7d9..51f37051 100644
--- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
@@ -34,7 +34,7 @@
Height="80"
MinWidth="50"
Margin="5,10,20,10"
- Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
+ Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
+ Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
ShowInputDialog(string title, SoftwareKeyboardUiArgs args)
+ public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args)
{
ContentDialog contentDialog = new();
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
index 01d97709..1fb9d3b3 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
@@ -11,8 +11,8 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.HOS;
-using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.Globalization;
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs
index 821d6fd9..ee15bc8d 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs
@@ -3,7 +3,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
using System;
namespace Ryujinx.Ava.UI.Controls
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs
index dd60503a..8681158f 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs
@@ -3,7 +3,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
using System;
namespace Ryujinx.Ava.UI.Controls
diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml
index c5041230..09fa0404 100644
--- a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml
@@ -26,7 +26,7 @@
Height="70"
MinWidth="50"
Margin="5,10,20,10"
- Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
+ Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
-
\ No newline at end of file
+
diff --git a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs
index cd63a99b..bc5622b5 100644
--- a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs
@@ -1,5 +1,5 @@
using Avalonia.Interactivity;
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
namespace Ryujinx.Ava.UI.Helpers
{
diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs
index b9d919f9..15b7ddd1 100644
--- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs
@@ -383,7 +383,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
result = ContentDialogResult.None;
- Logger.Warning?.Print(LogClass.Ui, "Content dialog overlay failed to populate. Default value has been returned.");
+ Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned.");
}
return result;
diff --git a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
index b61a924f..26fe36c4 100644
--- a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
@@ -1,7 +1,7 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common.Helper;
using System;
using System.Globalization;
diff --git a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs
index 0b178988..fc714541 100644
--- a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs
@@ -39,12 +39,12 @@ namespace Ryujinx.Ava.UI.Helpers
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
{
- GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, null));
+ GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
- GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, propertyValues));
+ GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
}
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)
diff --git a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs
index fc82bd6b..9a44b862 100644
--- a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs
@@ -1,6 +1,6 @@
using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ui.Common;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common;
+using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
diff --git a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
index 8340d39d..224f78f4 100644
--- a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
+++ b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
@@ -1,4 +1,4 @@
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
diff --git a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
index d53ff566..f0fb035d 100644
--- a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
+++ b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
@@ -1,4 +1,4 @@
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
index 7b476932..d6dea2f6 100644
--- a/src/Ryujinx.Ava/UI/Models/SaveModel.cs
+++ b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
@@ -3,8 +3,8 @@ using LibHac.Ncm;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common.Helper;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
index fa55c8d3..3bf19b43 100644
--- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
+++ b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
@@ -3,8 +3,8 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Common.Configuration;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;
diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs
index 769a1c91..3842301d 100644
--- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs
+++ b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs
@@ -3,7 +3,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using SPB.Graphics;
using SPB.Graphics.Exceptions;
using SPB.Graphics.OpenGL;
@@ -75,7 +75,7 @@ namespace Ryujinx.Ava.UI.Renderer
throw;
}
- Logger.Warning?.Print(LogClass.Ui, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
+ Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
}
}
diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
index 12c18e4a..d055d9ea 100644
--- a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Ryujinx.Common.Configuration;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Renderer
diff --git a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs
index 70ede4c1..6020f40e 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs
@@ -3,7 +3,7 @@ using Avalonia.Platform;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System;
using System.Net.Http;
using System.Net.NetworkInformation;
@@ -87,19 +87,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Version = Program.Version;
- if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
+ if (ConfigurationState.Instance.UI.BaseStyle.Value == "Light")
{
- GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
- DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
- PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
- TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
+ GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.UI.Common")));
+ DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.UI.Common")));
+ PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.UI.Common")));
+ TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.UI.Common")));
}
else
{
- GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
- DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
- PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
- TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
+ GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.UI.Common")));
+ DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.UI.Common")));
+ PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.UI.Common")));
+ TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.UI.Common")));
}
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs
index 0e0d858a..8f09568a 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs
@@ -9,7 +9,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
-using Ryujinx.Ui.Common.Models.Amiibo;
+using Ryujinx.UI.Common.Models.Amiibo;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_amiiboSeries = new ObservableCollection();
_amiibos = new AvaloniaList();
- _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
+ _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png");
_ = LoadContentAsync();
}
diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs
index 042803f3..71ad2c12 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs
@@ -19,7 +19,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -35,10 +35,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public class ControllerInputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
- private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
- private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg";
- private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg";
- private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg";
+ private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg";
+ private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg";
+ private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg";
+ private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg";
private const string KeyboardString = "keyboard";
private const string ControllerString = "controller";
private readonly MainWindow _mainWindow;
diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index 243d870a..17bd69b1 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -25,13 +25,13 @@ using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Input.HLE;
using Ryujinx.Modules;
-using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
@@ -138,7 +138,7 @@ namespace Ryujinx.Ava.UI.ViewModels
InputManager inputManager,
UserChannelPersistence userChannelPersistence,
LibHacHorizonManager libHacHorizonManager,
- IHostUiHandler uiHandler,
+ IHostUIHandler uiHandler,
Action showLoading,
Action switchToGameControl,
Action setMainContent,
@@ -685,10 +685,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool StartGamesInFullscreen
{
- get => ConfigurationState.Instance.Ui.StartFullscreen;
+ get => ConfigurationState.Instance.UI.StartFullscreen;
set
{
- ConfigurationState.Instance.Ui.StartFullscreen.Value = value;
+ ConfigurationState.Instance.UI.StartFullscreen.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
@@ -698,10 +698,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowConsole
{
- get => ConfigurationState.Instance.Ui.ShowConsole;
+ get => ConfigurationState.Instance.UI.ShowConsole;
set
{
- ConfigurationState.Instance.Ui.ShowConsole.Value = value;
+ ConfigurationState.Instance.UI.ShowConsole.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
@@ -743,10 +743,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public Glyph Glyph
{
- get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value;
+ get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value;
set
{
- ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value;
+ ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsGrid));
@@ -758,9 +758,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowNames
{
- get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set
+ get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set
{
- ConfigurationState.Instance.Ui.ShowNames.Value = value;
+ ConfigurationState.Instance.UI.ShowNames.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(GridSizeScale));
@@ -772,10 +772,10 @@ namespace Ryujinx.Ava.UI.ViewModels
internal ApplicationSort SortMode
{
- get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value;
+ get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value;
private set
{
- ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value;
+ ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortName));
@@ -788,7 +788,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
- return ConfigurationState.Instance.Ui.GridSize.Value switch
+ return ConfigurationState.Instance.UI.GridSize.Value switch
{
1 => 78,
2 => 100,
@@ -803,7 +803,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
- return ConfigurationState.Instance.Ui.GridSize.Value switch
+ return ConfigurationState.Instance.UI.GridSize.Value switch
{
1 => 120,
2 => ShowNames ? 210 : 150,
@@ -816,10 +816,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public int GridSizeScale
{
- get => ConfigurationState.Instance.Ui.GridSize;
+ get => ConfigurationState.Instance.UI.GridSize;
set
{
- ConfigurationState.Instance.Ui.GridSize.Value = value;
+ ConfigurationState.Instance.UI.GridSize.Value = value;
if (value < 2)
{
@@ -860,10 +860,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsAscending
{
- get => ConfigurationState.Instance.Ui.IsAscendingOrder;
+ get => ConfigurationState.Instance.UI.IsAscendingOrder;
private set
{
- ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value;
+ ConfigurationState.Instance.UI.IsAscendingOrder.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortMode));
@@ -919,7 +919,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public RendererHost RendererHostControl { get; private set; }
public bool IsClosing { get; set; }
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
- public IHostUiHandler UiHandler { get; internal set; }
+ public IHostUIHandler UiHandler { get; internal set; }
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
@@ -928,10 +928,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
- public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
- public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
- public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
- public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
+ public bool IsGridSmall => ConfigurationState.Instance.UI.GridSize == 1;
+ public bool IsGridMedium => ConfigurationState.Instance.UI.GridSize == 2;
+ public bool IsGridLarge => ConfigurationState.Instance.UI.GridSize == 3;
+ public bool IsGridHuge => ConfigurationState.Instance.UI.GridSize == 4;
#endregion
@@ -1245,7 +1245,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void LoadConfigurableHotKeys()
{
- if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
+ if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey))
{
ShowUiKey = new KeyGesture(showUiKey);
}
@@ -1385,7 +1385,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached)
{
- ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode;
+ ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
index 9e462a90..bcaa0860 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
@@ -16,8 +16,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Configuration.System;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Configuration.System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -408,9 +408,9 @@ namespace Ryujinx.Ava.UI.ViewModels
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
- GameDirectories.AddRange(config.Ui.GameDirs.Value);
+ GameDirectories.AddRange(config.UI.GameDirs.Value);
- BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
+ BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1;
// Input
EnableDockedMode = config.System.EnableDockedMode;
@@ -494,10 +494,10 @@ namespace Ryujinx.Ava.UI.ViewModels
if (_directoryChanged)
{
List gameDirs = new(GameDirectories);
- config.Ui.GameDirs.Value = gameDirs;
+ config.UI.GameDirs.Value = gameDirs;
}
- config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
+ config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
// Input
config.System.EnableDockedMode.Value = EnableDockedMode;
diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
index 8a287eca..5989ce09 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
@@ -17,7 +17,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.Ui.App.Common;
+using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
index 9c0e683a..8dff5086 100644
--- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
@@ -10,9 +10,9 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Modules;
-using Ryujinx.Ui.Common;
-using Ryujinx.Ui.Common.Configuration;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO;
@@ -43,7 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
checkBoxes.Add(new CheckBox
{
Content = $".{fileName}",
- IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
+ IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),
Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)),
});
}
diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs
index 822045d4..239a7cbf 100644
--- a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs
@@ -5,7 +5,7 @@ using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Views.Main
diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml
index a53c1dfe..b4eae01e 100644
--- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml
+++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml
@@ -45,7 +45,7 @@
diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
index a0657140..69fa8251 100644
--- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
@@ -51,7 +51,7 @@
Spacing="10">
ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
- "PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
- "XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
- "NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
- "NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
- "NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
+ "NSP" => ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSP,
+ "PFS0" => ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.UI.ShownFileTypes.PFS0,
+ "XCI" => ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.UI.ShownFileTypes.XCI,
+ "NCA" => ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NCA,
+ "NRO" => ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NRO,
+ "NSO" => ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
#pragma warning restore IDE0055
};
@@ -537,7 +537,7 @@ namespace Ryujinx.Ava.UI.Windows
Thread applicationLibraryThread = new(() =>
{
- ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language);
+ ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
_isLoading = false;
})
diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs
index 5de09ba0..d9ae0d4f 100644
--- a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs
@@ -6,7 +6,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
diff --git a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs b/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs
index 3cd12bc8..a12d2b3e 100644
--- a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs
+++ b/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs
@@ -4,7 +4,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
using System.IO;
using System.Reflection;
@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.UI.Windows
WindowStartupLocation = WindowStartupLocation.CenterOwner;
TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
- using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
+ using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
Icon = new WindowIcon(stream);
stream.Position = 0;
diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
index 7ece6335..f3ac6960 100644
--- a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
@@ -7,7 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.Ui.Common.Helper;
+using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
index b4f2f946..e9c163cf 100644
--- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
+++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
@@ -6,7 +6,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }
- public Key ShowUi { get; set; }
+ public Key ShowUI { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
public Key ResScaleUp { get; set; }
diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index f277dd06..1b404a06 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -70,7 +70,7 @@ namespace Ryujinx.Common.Logging
ServiceVi,
SurfaceFlinger,
TamperMachine,
- Ui,
+ UI,
Vic,
}
}
diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs
index f589bfdd..955fee4b 100644
--- a/src/Ryujinx.HLE/HLEConfiguration.cs
+++ b/src/Ryujinx.HLE/HLEConfiguration.cs
@@ -7,7 +7,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using System;
namespace Ryujinx.HLE
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE
/// The handler for various UI related operations needed outside of HLE.
///
/// This cannot be changed after instantiation.
- internal readonly IHostUiHandler HostUiHandler;
+ internal readonly IHostUIHandler HostUIHandler;
///
/// Control the memory configuration used by the emulation context.
@@ -177,7 +177,7 @@ namespace Ryujinx.HLE
IRenderer gpuRenderer,
IHardwareDeviceDriver audioDeviceDriver,
MemoryConfiguration memoryConfiguration,
- IHostUiHandler hostUiHandler,
+ IHostUIHandler hostUIHandler,
SystemLanguage systemLanguage,
RegionCode region,
bool enableVsync,
@@ -204,7 +204,7 @@ namespace Ryujinx.HLE
GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver;
MemoryConfiguration = memoryConfiguration;
- HostUiHandler = hostUiHandler;
+ HostUIHandler = hostUIHandler;
SystemLanguage = systemLanguage;
Region = region;
EnableVsync = enableVsync;
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs
index 86720217..5ec9d4b0 100644
--- a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Applets
PlayerIndex primaryIndex;
while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex))
{
- ControllerAppletUiArgs uiArgs = new()
+ ControllerAppletUIArgs uiArgs = new()
{
PlayerCountMin = playerMin,
PlayerCountMax = playerMax,
@@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Applets
IsDocked = _system.State.DockedMode,
};
- if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs))
+ if (!_system.Device.UIHandler.DisplayMessageDialog(uiArgs))
{
break;
}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs
new file mode 100644
index 00000000..10cba58b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Hid;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ public struct ControllerAppletUIArgs
+ {
+ public int PlayerCountMin;
+ public int PlayerCountMax;
+ public ControllerType SupportedStyles;
+ public IEnumerable SupportedPlayers;
+ public bool IsDocked;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
deleted file mode 100644
index bf440515..00000000
--- a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Hid;
-using System.Collections.Generic;
-
-namespace Ryujinx.HLE.HOS.Applets
-{
- public struct ControllerAppletUiArgs
- {
- public int PlayerCountMin;
- public int PlayerCountMax;
- public ControllerType SupportedStyles;
- public IEnumerable SupportedPlayers;
- public bool IsDocked;
- }
-}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
index 5c474f22..7ee9b9e9 100644
--- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
@@ -166,13 +166,13 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn");
- bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
+ bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
if (showDetails)
{
message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn");
- _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
+ _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
}
}
@@ -200,12 +200,12 @@ namespace Ryujinx.HLE.HOS.Applets.Error
buttons.Add("OK");
- bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
+ bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
if (showDetails)
{
buttons.RemoveAt(0);
- _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
+ _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
}
}
diff --git a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs
index 5ccf3994..985887c4 100644
--- a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs
@@ -1,5 +1,5 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
index 432bf6a8..0462a5b0 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
@@ -4,8 +4,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
-using Ryujinx.HLE.Ui;
-using Ryujinx.HLE.Ui.Input;
+using Ryujinx.HLE.UI;
+using Ryujinx.HLE.UI.Input;
using Ryujinx.Memory;
using System;
using System.Diagnostics;
@@ -92,14 +92,14 @@ namespace Ryujinx.HLE.HOS.Applets
_keyboardBackgroundInitialize = MemoryMarshal.Read(keyboardConfig);
_backgroundState = InlineKeyboardState.Uninitialized;
- if (_device.UiHandler == null)
+ if (_device.UIHandler == null)
{
Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly");
}
else
{
// Create a text handler that converts keyboard strokes to strings.
- _dynamicTextInputHandler = _device.UiHandler.CreateDynamicTextInputHandler();
+ _dynamicTextInputHandler = _device.UIHandler.CreateDynamicTextInputHandler();
_dynamicTextInputHandler.TextChangedEvent += HandleTextChangedEvent;
_dynamicTextInputHandler.KeyPressedEvent += HandleKeyPressedEvent;
@@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Applets
_npads.NpadButtonDownEvent += HandleNpadButtonDownEvent;
_npads.NpadButtonUpEvent += HandleNpadButtonUpEvent;
- _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UiHandler.HostUiTheme);
+ _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UIHandler.HostUITheme);
}
return ResultCode.Success;
@@ -199,7 +199,7 @@ namespace Ryujinx.HLE.HOS.Applets
_keyboardForegroundConfig.StringLengthMax = 100;
}
- if (_device.UiHandler == null)
+ if (_device.UIHandler == null)
{
Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default");
@@ -209,7 +209,7 @@ namespace Ryujinx.HLE.HOS.Applets
else
{
// Call the configured GUI handler to get user's input.
- var args = new SoftwareKeyboardUiArgs
+ var args = new SoftwareKeyboardUIArgs
{
KeyboardMode = _keyboardForegroundConfig.Mode,
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
@@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Applets
InitialText = initialText,
};
- _lastResult = _device.UiHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel;
+ _lastResult = _device.UIHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel;
_textValue ??= initialText ?? DefaultInputText;
}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
index f76cce29..3f7516e6 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
@@ -1,4 +1,4 @@
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using System;
using System.Threading;
@@ -15,13 +15,13 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private readonly object _stateLock = new();
- private readonly SoftwareKeyboardUiState _state = new();
+ private readonly SoftwareKeyboardUIState _state = new();
private readonly SoftwareKeyboardRendererBase _renderer;
private readonly TimedAction _textBoxBlinkTimedAction = new();
private readonly TimedAction _renderAction = new();
- public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
+ public SoftwareKeyboardRenderer(IHostUITheme uiTheme)
{
_renderer = new SoftwareKeyboardRendererBase(uiTheme);
@@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
StartRenderer(_renderAction, _renderer, _state, _stateLock);
}
- private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
+ private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUIState state, object stateLock)
{
timedAction.Reset(() =>
{
@@ -45,9 +45,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
}, TextBoxBlinkSleepMilliseconds);
}
- private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock)
+ private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUIState state, object stateLock)
{
- SoftwareKeyboardUiState internalState = new();
+ SoftwareKeyboardUIState internalState = new();
bool canCreateSurface = false;
bool needsUpdate = true;
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
index 3971a33b..75c648ff 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
@@ -1,4 +1,4 @@
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private Point _logoPosition;
private float _messagePositionY;
- public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
+ public SoftwareKeyboardRendererBase(IHostUITheme uiTheme)
{
int ryujinxLogoSize = 32;
@@ -205,7 +205,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
});
}
- public void DrawMutableElements(SoftwareKeyboardUiState state)
+ public void DrawMutableElements(SoftwareKeyboardUIState state)
{
if (_surface == null)
{
@@ -322,7 +322,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
}
- private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
+ private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state)
{
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs
new file mode 100644
index 00000000..854f04a3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs
@@ -0,0 +1,16 @@
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ public struct SoftwareKeyboardUIArgs
+ {
+ public KeyboardMode KeyboardMode;
+ public string HeaderText;
+ public string SubtitleText;
+ public string InitialText;
+ public string GuideText;
+ public string SubmitText;
+ public int StringLengthMin;
+ public int StringLengthMax;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs
new file mode 100644
index 00000000..6199ff66
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs
@@ -0,0 +1,22 @@
+using Ryujinx.HLE.UI;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ ///
+ /// TODO
+ ///
+ internal class SoftwareKeyboardUIState
+ {
+ public string InputText = "";
+ public int CursorBegin = 0;
+ public int CursorEnd = 0;
+ public bool AcceptPressed = false;
+ public bool CancelPressed = false;
+ public bool OverwriteMode = false;
+ public bool TypingEnabled = true;
+ public bool ControllerEnabled = true;
+ public int TextBoxBlinkCounter = 0;
+
+ public RenderingSurfaceInfo SurfaceInfo = null;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
deleted file mode 100644
index 52fa7ed8..00000000
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
-
-namespace Ryujinx.HLE.HOS.Applets
-{
- public struct SoftwareKeyboardUiArgs
- {
- public KeyboardMode KeyboardMode;
- public string HeaderText;
- public string SubtitleText;
- public string InitialText;
- public string GuideText;
- public string SubmitText;
- public int StringLengthMin;
- public int StringLengthMax;
- }
-}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs
deleted file mode 100644
index 608d51f3..00000000
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Ryujinx.HLE.Ui;
-
-namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
-{
- ///
- /// TODO
- ///
- internal class SoftwareKeyboardUiState
- {
- public string InputText = "";
- public int CursorBegin = 0;
- public int CursorEnd = 0;
- public bool AcceptPressed = false;
- public bool CancelPressed = false;
- public bool OverwriteMode = false;
- public bool TypingEnabled = true;
- public bool ControllerEnabled = true;
- public int TextBoxBlinkCounter = 0;
-
- public RenderingSurfaceInfo SurfaceInfo = null;
- }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
index 271d0060..9a7fdcc1 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
@@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
if (titleId == 0)
{
- context.Device.UiHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId);
+ context.Device.UIHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId);
}
else
{
@@ -524,7 +524,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value });
- context.Device.UiHandler.ExecuteProgram(context.Device, kind, value);
+ context.Device.UIHandler.ExecuteProgram(context.Device, kind, value);
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
index 143e2166..b6988f08 100644
--- a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
@@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService;
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types;
using Ryujinx.HLE.HOS.Services.Vi.Types;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index ae063a47..498714dc 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.Loaders.Processes;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using System;
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE
public PerformanceStatistics Statistics { get; }
public Hid Hid { get; }
public TamperMachine TamperMachine { get; }
- public IHostUiHandler UiHandler { get; }
+ public IHostUIHandler UIHandler { get; }
public bool EnableDeviceVsync { get; set; } = true;
@@ -39,7 +39,7 @@ namespace Ryujinx.HLE
Configuration = configuration;
FileSystem = Configuration.VirtualFileSystem;
- UiHandler = Configuration.HostUiHandler;
+ UIHandler = Configuration.HostUIHandler;
MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable
? MemoryAllocationFlags.Reserve
diff --git a/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs
new file mode 100644
index 00000000..c0945259
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.HLE.UI
+{
+ public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode);
+}
diff --git a/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs
new file mode 100644
index 00000000..1ff451d1
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.HLE.UI
+{
+ public interface IDynamicTextInputHandler : IDisposable
+ {
+ event DynamicTextChangedHandler TextChangedEvent;
+ event KeyPressedHandler KeyPressedEvent;
+ event KeyReleasedHandler KeyReleasedEvent;
+
+ bool TextProcessingEnabled { get; set; }
+
+ void SetText(string text, int cursorBegin);
+ void SetText(string text, int cursorBegin, int cursorEnd);
+ }
+}
diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
new file mode 100644
index 00000000..3b3a430e
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
@@ -0,0 +1,51 @@
+using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+
+namespace Ryujinx.HLE.UI
+{
+ public interface IHostUIHandler
+ {
+ ///
+ /// Displays an Input Dialog box to the user and blocks until text is entered.
+ ///
+ /// Text that the user entered. Set to `null` on internal errors
+ /// True when OK is pressed, False otherwise. Also returns True on internal errors
+ bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText);
+
+ ///
+ /// Displays a Message Dialog box to the user and blocks until it is closed.
+ ///
+ /// True when OK is pressed, False otherwise.
+ bool DisplayMessageDialog(string title, string message);
+
+ ///
+ /// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed.
+ ///
+ /// True when OK is pressed, False otherwise.
+ bool DisplayMessageDialog(ControllerAppletUIArgs args);
+
+ ///
+ /// Tell the UI that we need to transisition to another program.
+ ///
+ /// The device instance.
+ /// The program kind.
+ /// The value associated to the .
+ void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
+
+ /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
+ ///
+ /// False when OK is pressed, True when another button (Details) is pressed.
+ bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
+
+ ///
+ /// Creates a handler to process keyboard inputs into text strings.
+ ///
+ /// An instance of the text handler.
+ IDynamicTextInputHandler CreateDynamicTextInputHandler();
+
+ ///
+ /// Gets fonts and colors used by the host.
+ ///
+ IHostUITheme HostUITheme { get; }
+ }
+}
diff --git a/src/Ryujinx.HLE/UI/IHostUITheme.cs b/src/Ryujinx.HLE/UI/IHostUITheme.cs
new file mode 100644
index 00000000..3b054400
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/IHostUITheme.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.UI
+{
+ public interface IHostUITheme
+ {
+ string FontFamily { get; }
+
+ ThemeColor DefaultBackgroundColor { get; }
+ ThemeColor DefaultForegroundColor { get; }
+ ThemeColor DefaultBorderColor { get; }
+ ThemeColor SelectionBackgroundColor { get; }
+ ThemeColor SelectionForegroundColor { get; }
+ }
+}
diff --git a/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs
new file mode 100644
index 00000000..73c30661
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs
@@ -0,0 +1,6 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+
+namespace Ryujinx.HLE.UI.Input
+{
+ delegate void NpadButtonHandler(int npadIndex, NpadButton button);
+}
diff --git a/src/Ryujinx.HLE/UI/Input/NpadReader.cs b/src/Ryujinx.HLE/UI/Input/NpadReader.cs
new file mode 100644
index 00000000..8276d616
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/Input/NpadReader.cs
@@ -0,0 +1,140 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+
+namespace Ryujinx.HLE.UI.Input
+{
+ ///
+ /// Class that converts Hid entries for the Npad into pressed / released events.
+ ///
+ class NpadReader
+ {
+ private readonly Switch _device;
+ private readonly NpadCommonState[] _lastStates;
+
+ public event NpadButtonHandler NpadButtonUpEvent;
+ public event NpadButtonHandler NpadButtonDownEvent;
+
+ public NpadReader(Switch device)
+ {
+ _device = device;
+ _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length];
+ }
+
+ public NpadButton GetCurrentButtonsOfNpad(int npadIndex)
+ {
+ return _lastStates[npadIndex].Buttons;
+ }
+
+ public NpadButton GetCurrentButtonsOfAllNpads()
+ {
+ NpadButton buttons = 0;
+
+ foreach (var state in _lastStates)
+ {
+ buttons |= state.Buttons;
+ }
+
+ return buttons;
+ }
+
+ private static ref RingLifo GetCommonStateLifo(ref NpadInternalState npad)
+ {
+ switch (npad.StyleSet)
+ {
+ case NpadStyleTag.FullKey:
+ return ref npad.FullKey;
+ case NpadStyleTag.Handheld:
+ return ref npad.Handheld;
+ case NpadStyleTag.JoyDual:
+ return ref npad.JoyDual;
+ case NpadStyleTag.JoyLeft:
+ return ref npad.JoyLeft;
+ case NpadStyleTag.JoyRight:
+ return ref npad.JoyRight;
+ case NpadStyleTag.Palma:
+ return ref npad.Palma;
+ default:
+ return ref npad.SystemExt;
+ }
+ }
+
+ public void Update(bool supressEvents = false)
+ {
+ ref var npads = ref _device.Hid.SharedMemory.Npads;
+
+ // Process each input individually.
+ for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++)
+ {
+ UpdateNpad(npadIndex, supressEvents);
+ }
+ }
+
+ private void UpdateNpad(int npadIndex, bool supressEvents)
+ {
+ const int MaxEntries = 1024;
+
+ ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex];
+ ref var lastEntry = ref _lastStates[npadIndex];
+
+ var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries);
+
+ int firstEntryNum;
+
+ // Scan the LIFO for the first entry that is newer that what's already processed.
+ for (firstEntryNum = fullKeyEntries.Length - 1;
+ firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber;
+ firstEntryNum--)
+ {
+ }
+
+ if (firstEntryNum == -1)
+ {
+ return;
+ }
+
+ for (; firstEntryNum >= 0; firstEntryNum--)
+ {
+ var entry = fullKeyEntries[firstEntryNum];
+
+ // The interval of valid entries should be contiguous.
+ if (entry.SamplingNumber < lastEntry.SamplingNumber)
+ {
+ break;
+ }
+
+ if (!supressEvents)
+ {
+ ProcessNpadButtons(npadIndex, entry.Object.Buttons);
+ }
+
+ lastEntry = entry.Object;
+ }
+ }
+
+ private void ProcessNpadButtons(int npadIndex, NpadButton buttons)
+ {
+ NpadButton lastButtons = _lastStates[npadIndex].Buttons;
+
+ for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1)
+ {
+ NpadButton currentButton = (NpadButton)buttonMask & buttons;
+ NpadButton lastButton = (NpadButton)buttonMask & lastButtons;
+
+ if (lastButton != 0)
+ {
+ if (currentButton == 0)
+ {
+ NpadButtonUpEvent?.Invoke(npadIndex, lastButton);
+ }
+ }
+ else
+ {
+ if (currentButton != 0)
+ {
+ NpadButtonDownEvent?.Invoke(npadIndex, currentButton);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/UI/KeyPressedHandler.cs b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs
new file mode 100644
index 00000000..6feb11bd
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs
@@ -0,0 +1,6 @@
+using Ryujinx.Common.Configuration.Hid;
+
+namespace Ryujinx.HLE.UI
+{
+ public delegate bool KeyPressedHandler(Key key);
+}
diff --git a/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs
new file mode 100644
index 00000000..3de89d0c
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs
@@ -0,0 +1,6 @@
+using Ryujinx.Common.Configuration.Hid;
+
+namespace Ryujinx.HLE.UI
+{
+ public delegate bool KeyReleasedHandler(Key key);
+}
diff --git a/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs
new file mode 100644
index 00000000..af0a0d44
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs
@@ -0,0 +1,45 @@
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
+using System;
+
+namespace Ryujinx.HLE.UI
+{
+ ///
+ /// Information about the indirect layer that is being drawn to.
+ ///
+ class RenderingSurfaceInfo : IEquatable
+ {
+ public ColorFormat ColorFormat { get; }
+ public uint Width { get; }
+ public uint Height { get; }
+ public uint Pitch { get; }
+ public uint Size { get; }
+
+ public RenderingSurfaceInfo(ColorFormat colorFormat, uint width, uint height, uint pitch, uint size)
+ {
+ ColorFormat = colorFormat;
+ Width = width;
+ Height = height;
+ Pitch = pitch;
+ Size = size;
+ }
+
+ public bool Equals(RenderingSurfaceInfo other)
+ {
+ return ColorFormat == other.ColorFormat &&
+ Width == other.Width &&
+ Height == other.Height &&
+ Pitch == other.Pitch &&
+ Size == other.Size;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is RenderingSurfaceInfo info && Equals(info);
+ }
+
+ public override int GetHashCode()
+ {
+ return BitConverter.ToInt32(BitConverter.GetBytes(((ulong)ColorFormat) ^ Width ^ Height ^ Pitch ^ Size));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/UI/ThemeColor.cs b/src/Ryujinx.HLE/UI/ThemeColor.cs
new file mode 100644
index 00000000..c5cfb147
--- /dev/null
+++ b/src/Ryujinx.HLE/UI/ThemeColor.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.UI
+{
+ public readonly struct ThemeColor
+ {
+ public float A { get; }
+ public float R { get; }
+ public float G { get; }
+ public float B { get; }
+
+ public ThemeColor(float a, float r, float g, float b)
+ {
+ A = a;
+ R = r;
+ G = g;
+ B = b;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs b/src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs
deleted file mode 100644
index cb9ca0de..00000000
--- a/src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Ryujinx.HLE.Ui
-{
- public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode);
-}
diff --git a/src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs b/src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs
deleted file mode 100644
index e530d2c4..00000000
--- a/src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace Ryujinx.HLE.Ui
-{
- public interface IDynamicTextInputHandler : IDisposable
- {
- event DynamicTextChangedHandler TextChangedEvent;
- event KeyPressedHandler KeyPressedEvent;
- event KeyReleasedHandler KeyReleasedEvent;
-
- bool TextProcessingEnabled { get; set; }
-
- void SetText(string text, int cursorBegin);
- void SetText(string text, int cursorBegin, int cursorEnd);
- }
-}
diff --git a/src/Ryujinx.HLE/Ui/IHostUiHandler.cs b/src/Ryujinx.HLE/Ui/IHostUiHandler.cs
deleted file mode 100644
index 68f78f22..00000000
--- a/src/Ryujinx.HLE/Ui/IHostUiHandler.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using Ryujinx.HLE.HOS.Applets;
-using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
-
-namespace Ryujinx.HLE.Ui
-{
- public interface IHostUiHandler
- {
- ///
- /// Displays an Input Dialog box to the user and blocks until text is entered.
- ///
- /// Text that the user entered. Set to `null` on internal errors
- /// True when OK is pressed, False otherwise. Also returns True on internal errors
- bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText);
-
- ///
- /// Displays a Message Dialog box to the user and blocks until it is closed.
- ///
- /// True when OK is pressed, False otherwise.
- bool DisplayMessageDialog(string title, string message);
-
- ///
- /// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed.
- ///
- /// True when OK is pressed, False otherwise.
- bool DisplayMessageDialog(ControllerAppletUiArgs args);
-
- ///
- /// Tell the UI that we need to transisition to another program.
- ///
- /// The device instance.
- /// The program kind.
- /// The value associated to the .
- void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
-
- /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
- ///
- /// False when OK is pressed, True when another button (Details) is pressed.
- bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
-
- ///
- /// Creates a handler to process keyboard inputs into text strings.
- ///
- /// An instance of the text handler.
- IDynamicTextInputHandler CreateDynamicTextInputHandler();
-
- ///
- /// Gets fonts and colors used by the host.
- ///
- IHostUiTheme HostUiTheme { get; }
- }
-}
diff --git a/src/Ryujinx.HLE/Ui/IHostUiTheme.cs b/src/Ryujinx.HLE/Ui/IHostUiTheme.cs
deleted file mode 100644
index 11d82361..00000000
--- a/src/Ryujinx.HLE/Ui/IHostUiTheme.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Ryujinx.HLE.Ui
-{
- public interface IHostUiTheme
- {
- string FontFamily { get; }
-
- ThemeColor DefaultBackgroundColor { get; }
- ThemeColor DefaultForegroundColor { get; }
- ThemeColor DefaultBorderColor { get; }
- ThemeColor SelectionBackgroundColor { get; }
- ThemeColor SelectionForegroundColor { get; }
- }
-}
diff --git a/src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs b/src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs
deleted file mode 100644
index 2d1c1c49..00000000
--- a/src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
-
-namespace Ryujinx.HLE.Ui.Input
-{
- delegate void NpadButtonHandler(int npadIndex, NpadButton button);
-}
diff --git a/src/Ryujinx.HLE/Ui/Input/NpadReader.cs b/src/Ryujinx.HLE/Ui/Input/NpadReader.cs
deleted file mode 100644
index 8fc95dc9..00000000
--- a/src/Ryujinx.HLE/Ui/Input/NpadReader.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
-using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
-
-namespace Ryujinx.HLE.Ui.Input
-{
- ///
- /// Class that converts Hid entries for the Npad into pressed / released events.
- ///
- class NpadReader
- {
- private readonly Switch _device;
- private readonly NpadCommonState[] _lastStates;
-
- public event NpadButtonHandler NpadButtonUpEvent;
- public event NpadButtonHandler NpadButtonDownEvent;
-
- public NpadReader(Switch device)
- {
- _device = device;
- _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length];
- }
-
- public NpadButton GetCurrentButtonsOfNpad(int npadIndex)
- {
- return _lastStates[npadIndex].Buttons;
- }
-
- public NpadButton GetCurrentButtonsOfAllNpads()
- {
- NpadButton buttons = 0;
-
- foreach (var state in _lastStates)
- {
- buttons |= state.Buttons;
- }
-
- return buttons;
- }
-
- private static ref RingLifo GetCommonStateLifo(ref NpadInternalState npad)
- {
- switch (npad.StyleSet)
- {
- case NpadStyleTag.FullKey:
- return ref npad.FullKey;
- case NpadStyleTag.Handheld:
- return ref npad.Handheld;
- case NpadStyleTag.JoyDual:
- return ref npad.JoyDual;
- case NpadStyleTag.JoyLeft:
- return ref npad.JoyLeft;
- case NpadStyleTag.JoyRight:
- return ref npad.JoyRight;
- case NpadStyleTag.Palma:
- return ref npad.Palma;
- default:
- return ref npad.SystemExt;
- }
- }
-
- public void Update(bool supressEvents = false)
- {
- ref var npads = ref _device.Hid.SharedMemory.Npads;
-
- // Process each input individually.
- for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++)
- {
- UpdateNpad(npadIndex, supressEvents);
- }
- }
-
- private void UpdateNpad(int npadIndex, bool supressEvents)
- {
- const int MaxEntries = 1024;
-
- ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex];
- ref var lastEntry = ref _lastStates[npadIndex];
-
- var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries);
-
- int firstEntryNum;
-
- // Scan the LIFO for the first entry that is newer that what's already processed.
- for (firstEntryNum = fullKeyEntries.Length - 1;
- firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber;
- firstEntryNum--)
- {
- }
-
- if (firstEntryNum == -1)
- {
- return;
- }
-
- for (; firstEntryNum >= 0; firstEntryNum--)
- {
- var entry = fullKeyEntries[firstEntryNum];
-
- // The interval of valid entries should be contiguous.
- if (entry.SamplingNumber < lastEntry.SamplingNumber)
- {
- break;
- }
-
- if (!supressEvents)
- {
- ProcessNpadButtons(npadIndex, entry.Object.Buttons);
- }
-
- lastEntry = entry.Object;
- }
- }
-
- private void ProcessNpadButtons(int npadIndex, NpadButton buttons)
- {
- NpadButton lastButtons = _lastStates[npadIndex].Buttons;
-
- for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1)
- {
- NpadButton currentButton = (NpadButton)buttonMask & buttons;
- NpadButton lastButton = (NpadButton)buttonMask & lastButtons;
-
- if (lastButton != 0)
- {
- if (currentButton == 0)
- {
- NpadButtonUpEvent?.Invoke(npadIndex, lastButton);
- }
- }
- else
- {
- if (currentButton != 0)
- {
- NpadButtonDownEvent?.Invoke(npadIndex, currentButton);
- }
- }
- }
- }
- }
-}
diff --git a/src/Ryujinx.HLE/Ui/KeyPressedHandler.cs b/src/Ryujinx.HLE/Ui/KeyPressedHandler.cs
deleted file mode 100644
index 31e75437..00000000
--- a/src/Ryujinx.HLE/Ui/KeyPressedHandler.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Ryujinx.Common.Configuration.Hid;
-
-namespace Ryujinx.HLE.Ui
-{
- public delegate bool KeyPressedHandler(Key key);
-}
diff --git a/src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs b/src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs
deleted file mode 100644
index d5b6d201..00000000
--- a/src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Ryujinx.Common.Configuration.Hid;
-
-namespace Ryujinx.HLE.Ui
-{
- public delegate bool KeyReleasedHandler(Key key);
-}
diff --git a/src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs b/src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs
deleted file mode 100644
index 0b3d0a90..00000000
--- a/src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
-using System;
-
-namespace Ryujinx.HLE.Ui
-{
- ///
- /// Information about the indirect layer that is being drawn to.
- ///
- class RenderingSurfaceInfo : IEquatable
- {
- public ColorFormat ColorFormat { get; }
- public uint Width { get; }
- public uint Height { get; }
- public uint Pitch { get; }
- public uint Size { get; }
-
- public RenderingSurfaceInfo(ColorFormat colorFormat, uint width, uint height, uint pitch, uint size)
- {
- ColorFormat = colorFormat;
- Width = width;
- Height = height;
- Pitch = pitch;
- Size = size;
- }
-
- public bool Equals(RenderingSurfaceInfo other)
- {
- return ColorFormat == other.ColorFormat &&
- Width == other.Width &&
- Height == other.Height &&
- Pitch == other.Pitch &&
- Size == other.Size;
- }
-
- public override bool Equals(object obj)
- {
- return obj is RenderingSurfaceInfo info && Equals(info);
- }
-
- public override int GetHashCode()
- {
- return BitConverter.ToInt32(BitConverter.GetBytes(((ulong)ColorFormat) ^ Width ^ Height ^ Pitch ^ Size));
- }
- }
-}
diff --git a/src/Ryujinx.HLE/Ui/ThemeColor.cs b/src/Ryujinx.HLE/Ui/ThemeColor.cs
deleted file mode 100644
index 23657ed2..00000000
--- a/src/Ryujinx.HLE/Ui/ThemeColor.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Ryujinx.HLE.Ui
-{
- public readonly struct ThemeColor
- {
- public float A { get; }
- public float R { get; }
- public float G { get; }
- public float B { get; }
-
- public ThemeColor(float a, float r, float g, float b)
- {
- A = a;
- R = r;
- G = g;
- B = b;
- }
- }
-}
diff --git a/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs
index aae01a0c..503874ff 100644
--- a/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs
+++ b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs
@@ -1,4 +1,4 @@
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using System.Threading;
using System.Threading.Tasks;
diff --git a/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs
index a2df6f3e..78cd43ae 100644
--- a/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs
+++ b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs
@@ -1,8 +1,8 @@
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
namespace Ryujinx.Headless.SDL2
{
- internal class HeadlessHostUiTheme : IHostUiTheme
+ internal class HeadlessHostUiTheme : IHostUITheme
{
public string FontFamily => "sans-serif";
diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs
index b1f43dc2..8768913f 100644
--- a/src/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -7,7 +7,7 @@ using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
-using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.UI;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
@@ -25,7 +25,7 @@ using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
{
- abstract partial class WindowBase : IHostUiHandler, IDisposable
+ abstract partial class WindowBase : IHostUIHandler, IDisposable
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
@@ -53,7 +53,7 @@ namespace Ryujinx.Headless.SDL2
protected IntPtr WindowHandle { get; set; }
- public IHostUiTheme HostUiTheme { get; }
+ public IHostUITheme HostUITheme { get; }
public int Width { get; private set; }
public int Height { get; private set; }
public int DisplayId { get; set; }
@@ -106,7 +106,7 @@ namespace Ryujinx.Headless.SDL2
_gpuDoneEvent = new ManualResetEvent(false);
_aspectRatio = aspectRatio;
_enableMouse = enableMouse;
- HostUiTheme = new HeadlessHostUiTheme();
+ HostUITheme = new HeadlessHostUiTheme();
SDL2Driver.Instance.Initialize();
}
@@ -465,7 +465,7 @@ namespace Ryujinx.Headless.SDL2
Exit();
}
- public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
+ public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{
// SDL2 doesn't support input dialogs
userText = "Ryujinx";
@@ -480,7 +480,7 @@ namespace Ryujinx.Headless.SDL2
return true;
}
- public bool DisplayMessageDialog(ControllerAppletUiArgs args)
+ public bool DisplayMessageDialog(ControllerAppletUIArgs args)
{
string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
diff --git a/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs
new file mode 100644
index 00000000..58e066b9
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.UI.App.Common
+{
+ public class ApplicationAddedEventArgs : EventArgs
+ {
+ public ApplicationData AppData { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs
new file mode 100644
index 00000000..5ed7baf1
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Ryujinx.UI.App.Common
+{
+ public class ApplicationCountUpdatedEventArgs : EventArgs
+ {
+ public int NumAppsFound { get; set; }
+ public int NumAppsLoaded { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs
new file mode 100644
index 00000000..8cc7238e
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs
@@ -0,0 +1,158 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Loader;
+using LibHac.Ns;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.UI.Common.Helper;
+using System;
+using System.IO;
+
+namespace Ryujinx.UI.App.Common
+{
+ public class ApplicationData
+ {
+ public bool Favorite { get; set; }
+ public byte[] Icon { get; set; }
+ public string TitleName { get; set; }
+ public string TitleId { get; set; }
+ public string Developer { get; set; }
+ public string Version { get; set; }
+ public TimeSpan TimePlayed { get; set; }
+ public DateTime? LastPlayed { get; set; }
+ public string FileExtension { get; set; }
+ public long FileSize { get; set; }
+ public string Path { get; set; }
+ public BlitStruct ControlHolder { get; set; }
+
+ public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
+
+ public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed);
+
+ public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
+
+ public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
+ {
+ using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
+
+ Nca mainNca = null;
+ Nca patchNca = null;
+
+ if (!System.IO.Path.Exists(titleFilePath))
+ {
+ Logger.Error?.Print(LogClass.Application, $"File does not exists. {titleFilePath}");
+ return string.Empty;
+ }
+
+ string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
+
+ if (extension is ".nsp" or ".xci")
+ {
+ IFileSystem pfs;
+
+ if (extension == ".xci")
+ {
+ Xci xci = new(virtualFileSystem.KeySet, file.AsStorage());
+
+ pfs = xci.OpenPartition(XciPartitionType.Secure);
+ }
+ else
+ {
+ var pfsTemp = new PartitionFileSystem();
+ pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
+ 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 != NcaContentType.Program)
+ {
+ continue;
+ }
+
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+ {
+ patchNca = nca;
+ }
+ else
+ {
+ mainNca = nca;
+ }
+ }
+ }
+ else if (extension == ".nca")
+ {
+ mainNca = new Nca(virtualFileSystem.KeySet, file.AsStorage());
+ }
+
+ if (mainNca == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
+
+ return string.Empty;
+ }
+
+ (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
+
+ if (updatePatchNca != null)
+ {
+ patchNca = updatePatchNca;
+ }
+
+ IFileSystem codeFs = null;
+
+ if (patchNca == null)
+ {
+ if (mainNca.CanOpenSection(NcaSectionType.Code))
+ {
+ codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
+ }
+ }
+ else
+ {
+ if (patchNca.CanOpenSection(NcaSectionType.Code))
+ {
+ codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
+ }
+ }
+
+ if (codeFs == null)
+ {
+ Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
+
+ return string.Empty;
+ }
+
+ const string MainExeFs = "main";
+
+ if (!codeFs.FileExists($"/{MainExeFs}"))
+ {
+ Logger.Error?.Print(LogClass.Loader, "No main binary ExeFS found in ExeFS");
+
+ return string.Empty;
+ }
+
+ using var nsoFile = new UniqueRef();
+
+ codeFs.OpenFile(ref nsoFile.Ref, $"/{MainExeFs}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ NsoReader reader = new();
+ reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure();
+
+ return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", "").ToUpper()[..16];
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs
new file mode 100644
index 00000000..ada7cc34
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.App.Common
+{
+ [JsonSourceGenerationOptions(WriteIndented = true)]
+ [JsonSerializable(typeof(ApplicationMetadata))]
+ internal partial class ApplicationJsonSerializerContext : JsonSerializerContext
+ {
+ }
+}
diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
new file mode 100644
index 00000000..65cf7a9e
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
@@ -0,0 +1,930 @@
+using LibHac;
+using LibHac.Common;
+using LibHac.Common.Keys;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Ns;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Configuration.System;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using Path = System.IO.Path;
+using TimeSpan = System.TimeSpan;
+
+namespace Ryujinx.UI.App.Common
+{
+ public class ApplicationLibrary
+ {
+ public event EventHandler ApplicationAdded;
+ public event EventHandler ApplicationCountUpdated;
+
+ private readonly byte[] _nspIcon;
+ private readonly byte[] _xciIcon;
+ private readonly byte[] _ncaIcon;
+ private readonly byte[] _nroIcon;
+ private readonly byte[] _nsoIcon;
+
+ private readonly VirtualFileSystem _virtualFileSystem;
+ private Language _desiredTitleLanguage;
+ private CancellationTokenSource _cancellationToken;
+
+ private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+ private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+
+ public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
+ {
+ _virtualFileSystem = virtualFileSystem;
+
+ _nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png");
+ _xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png");
+ _ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png");
+ _nroIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NRO.png");
+ _nsoIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSO.png");
+ }
+
+ private static byte[] GetResourceBytes(string resourceName)
+ {
+ Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
+ byte[] resourceByteArray = new byte[resourceStream.Length];
+
+ resourceStream.Read(resourceByteArray);
+
+ return resourceByteArray;
+ }
+
+ public void CancelLoading()
+ {
+ _cancellationToken?.Cancel();
+ }
+
+ public static void ReadControlData(IFileSystem controlFs, Span outProperty)
+ {
+ using UniqueRef controlFile = new();
+
+ controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
+ }
+
+ public void LoadApplications(List appDirs, Language desiredTitleLanguage)
+ {
+ int numApplicationsFound = 0;
+ int numApplicationsLoaded = 0;
+
+ _desiredTitleLanguage = desiredTitleLanguage;
+
+ _cancellationToken = new CancellationTokenSource();
+
+ // Builds the applications list with paths to found applications
+ List applications = new();
+
+ try
+ {
+ foreach (string appDir in appDirs)
+ {
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ if (!Directory.Exists(appDir))
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid directory: \"{appDir}\"");
+
+ continue;
+ }
+
+ try
+ {
+ IEnumerable files = Directory.EnumerateFiles(appDir, "*", SearchOption.AllDirectories).Where(file =>
+ {
+ return
+ (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) ||
+ (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value) ||
+ (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value);
+ });
+
+ foreach (string app in files)
+ {
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var fileInfo = new FileInfo(app);
+ string extension = fileInfo.Extension.ToLower();
+
+ if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
+ {
+ var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
+
+ if (!File.Exists(fullPath))
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Skipping invalid symlink: {fileInfo.FullName}");
+ continue;
+ }
+
+ applications.Add(fullPath);
+ numApplicationsFound++;
+ }
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Failed to get access to directory: \"{appDir}\"");
+ }
+ }
+
+ // Loops through applications list, creating a struct and then firing an event containing the struct for each application
+ foreach (string applicationPath in applications)
+ {
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ long fileSize = new FileInfo(applicationPath).Length;
+ string titleName = "Unknown";
+ string titleId = "0000000000000000";
+ string developer = "Unknown";
+ string version = "0";
+ byte[] applicationIcon = null;
+
+ BlitStruct controlHolder = new(1);
+
+ try
+ {
+ string extension = Path.GetExtension(applicationPath).ToLower();
+
+ using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
+
+ if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
+ {
+ try
+ {
+ IFileSystem pfs;
+
+ bool isExeFs = false;
+
+ if (extension == ".xci")
+ {
+ Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
+
+ pfs = xci.OpenPartition(XciPartitionType.Secure);
+ }
+ else
+ {
+ var pfsTemp = new PartitionFileSystem();
+ pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
+ pfs = pfsTemp;
+
+ // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
+ bool hasMainNca = false;
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
+ {
+ if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
+ {
+ using UniqueRef ncaFile = new();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ // Some main NCAs don't have a data partition, so check if the partition exists before opening it
+ if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
+ {
+ hasMainNca = true;
+
+ break;
+ }
+ }
+ else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
+ {
+ isExeFs = true;
+ }
+ }
+
+ if (!hasMainNca && !isExeFs)
+ {
+ numApplicationsFound--;
+
+ continue;
+ }
+ }
+
+ if (isExeFs)
+ {
+ applicationIcon = _nspIcon;
+
+ using UniqueRef npdmFile = new();
+
+ Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
+
+ if (ResultFs.PathNotFound.Includes(result))
+ {
+ Npdm npdm = new(npdmFile.Get.AsStream());
+
+ titleName = npdm.TitleName;
+ titleId = npdm.Aci0.TitleId.ToString("x16");
+ }
+ }
+ else
+ {
+ GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
+
+ // Check if there is an update available.
+ if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
+ {
+ // Replace the original ControlFs by the updated one.
+ controlFs = updatedControlFs;
+ }
+
+ ReadControlData(controlFs, controlHolder.ByteSpan);
+
+ GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
+
+ // Read the icon from the ControlFS and store it as a byte array
+ try
+ {
+ using UniqueRef icon = new();
+
+ controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using MemoryStream stream = new();
+
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+ }
+ catch (HorizonResultException)
+ {
+ foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
+ {
+ if (entry.Name == "control.nacp")
+ {
+ continue;
+ }
+
+ using var icon = new UniqueRef();
+
+ controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using MemoryStream stream = new();
+
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+
+ if (applicationIcon != null)
+ {
+ break;
+ }
+ }
+
+ applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
+ }
+ }
+ }
+ catch (MissingKeyException exception)
+ {
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
+
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
+ }
+ catch (InvalidDataException)
+ {
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
+
+ Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
+ }
+ catch (Exception exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
+
+ numApplicationsFound--;
+
+ continue;
+ }
+ }
+ else if (extension == ".nro")
+ {
+ BinaryReader reader = new(file);
+
+ byte[] Read(long position, int size)
+ {
+ file.Seek(position, SeekOrigin.Begin);
+
+ return reader.ReadBytes(size);
+ }
+
+ try
+ {
+ file.Seek(24, SeekOrigin.Begin);
+
+ int assetOffset = reader.ReadInt32();
+
+ if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
+ {
+ byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
+
+ long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
+ long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
+
+ ulong nacpOffset = reader.ReadUInt64();
+ ulong nacpSize = reader.ReadUInt64();
+
+ // Reads and stores game icon as byte array
+ if (iconSize > 0)
+ {
+ applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
+ }
+ else
+ {
+ applicationIcon = _nroIcon;
+ }
+
+ // Read the NACP data
+ Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
+
+ GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
+ }
+ else
+ {
+ applicationIcon = _nroIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
+ }
+ }
+ catch
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
+
+ numApplicationsFound--;
+
+ continue;
+ }
+ }
+ else if (extension == ".nca")
+ {
+ try
+ {
+ Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
+ {
+ numApplicationsFound--;
+
+ continue;
+ }
+ }
+ catch (InvalidDataException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}");
+ }
+ catch
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
+
+ numApplicationsFound--;
+
+ continue;
+ }
+
+ applicationIcon = _ncaIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
+ }
+ // If its an NSO we just set defaults
+ else if (extension == ".nso")
+ {
+ applicationIcon = _nsoIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
+ }
+ }
+ catch (IOException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, exception.Message);
+
+ numApplicationsFound--;
+
+ continue;
+ }
+
+ ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
+ {
+ appMetadata.Title = titleName;
+
+ // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
+ if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
+ {
+ appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
+ appMetadata.TimePlayedOld = default;
+ }
+
+ // Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
+ if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
+ {
+ // Migrate from string-based last_played to DateTime-based last_played_utc.
+ if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
+ {
+ appMetadata.LastPlayed = lastPlayedOldParsed;
+
+ // Migration successful: deleting last_played from the metadata file.
+ appMetadata.LastPlayedOld = default;
+ }
+
+ }
+ });
+
+ ApplicationData data = new()
+ {
+ Favorite = appMetadata.Favorite,
+ Icon = applicationIcon,
+ TitleName = titleName,
+ TitleId = titleId,
+ Developer = developer,
+ Version = version,
+ TimePlayed = appMetadata.TimePlayed,
+ LastPlayed = appMetadata.LastPlayed,
+ FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(),
+ FileSize = fileSize,
+ Path = applicationPath,
+ ControlHolder = controlHolder,
+ };
+
+ numApplicationsLoaded++;
+
+ OnApplicationAdded(new ApplicationAddedEventArgs
+ {
+ AppData = data,
+ });
+
+ OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
+ {
+ NumAppsFound = numApplicationsFound,
+ NumAppsLoaded = numApplicationsLoaded,
+ });
+ }
+
+ OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
+ {
+ NumAppsFound = numApplicationsFound,
+ NumAppsLoaded = numApplicationsLoaded,
+ });
+ }
+ finally
+ {
+ _cancellationToken.Dispose();
+ _cancellationToken = null;
+ }
+ }
+
+ protected void OnApplicationAdded(ApplicationAddedEventArgs e)
+ {
+ ApplicationAdded?.Invoke(null, e);
+ }
+
+ protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
+ {
+ ApplicationCountUpdated?.Invoke(null, e);
+ }
+
+ private void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem controlFs, out string titleId)
+ {
+ (_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
+
+ // Return the ControlFS
+ controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+ titleId = controlNca?.Header.TitleId.ToString("x16");
+ }
+
+ public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null)
+ {
+ string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
+ string metadataFile = Path.Combine(metadataFolder, "metadata.json");
+
+ ApplicationMetadata appMetadata;
+
+ if (!File.Exists(metadataFile))
+ {
+ Directory.CreateDirectory(metadataFolder);
+
+ appMetadata = new ApplicationMetadata();
+
+ JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata);
+ }
+
+ try
+ {
+ appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
+ }
+ catch (JsonException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
+
+ appMetadata = new ApplicationMetadata();
+ }
+
+ if (modifyFunction != null)
+ {
+ modifyFunction(appMetadata);
+
+ JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata);
+ }
+
+ return appMetadata;
+ }
+
+ public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage)
+ {
+ byte[] applicationIcon = null;
+
+ try
+ {
+ // Look for icon only if applicationPath is not a directory
+ if (!Directory.Exists(applicationPath))
+ {
+ string extension = Path.GetExtension(applicationPath).ToLower();
+
+ using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
+
+ if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
+ {
+ try
+ {
+ IFileSystem pfs;
+
+ bool isExeFs = false;
+
+ if (extension == ".xci")
+ {
+ Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
+
+ pfs = xci.OpenPartition(XciPartitionType.Secure);
+ }
+ else
+ {
+ var pfsTemp = new PartitionFileSystem();
+ pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
+ pfs = pfsTemp;
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
+ {
+ if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
+ {
+ isExeFs = true;
+ }
+ }
+ }
+
+ if (isExeFs)
+ {
+ applicationIcon = _nspIcon;
+ }
+ else
+ {
+ // Store the ControlFS in variable called controlFs
+ GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out _);
+
+ // Read the icon from the ControlFS and store it as a byte array
+ try
+ {
+ using var icon = new UniqueRef();
+
+ controlFs.OpenFile(ref icon.Ref, $"/icon_{desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using MemoryStream stream = new();
+
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+ }
+ catch (HorizonResultException)
+ {
+ foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
+ {
+ if (entry.Name == "control.nacp")
+ {
+ continue;
+ }
+
+ using var icon = new UniqueRef();
+
+ controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using (MemoryStream stream = new())
+ {
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+ }
+
+ if (applicationIcon != null)
+ {
+ break;
+ }
+ }
+
+ applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
+ }
+ }
+ }
+ catch (MissingKeyException)
+ {
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
+ }
+ catch (InvalidDataException)
+ {
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
+ }
+ catch (Exception exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
+ }
+ }
+ else if (extension == ".nro")
+ {
+ BinaryReader reader = new(file);
+
+ byte[] Read(long position, int size)
+ {
+ file.Seek(position, SeekOrigin.Begin);
+
+ return reader.ReadBytes(size);
+ }
+
+ try
+ {
+ file.Seek(24, SeekOrigin.Begin);
+
+ int assetOffset = reader.ReadInt32();
+
+ if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
+ {
+ byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
+
+ long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
+ long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
+
+ // Reads and stores game icon as byte array
+ if (iconSize > 0)
+ {
+ applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
+ }
+ else
+ {
+ applicationIcon = _nroIcon;
+ }
+ }
+ else
+ {
+ applicationIcon = _nroIcon;
+ }
+ }
+ catch
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
+ }
+ }
+ else if (extension == ".nca")
+ {
+ applicationIcon = _ncaIcon;
+ }
+ // If its an NSO we just set defaults
+ else if (extension == ".nso")
+ {
+ applicationIcon = _nsoIcon;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
+ }
+
+ return applicationIcon ?? _ncaIcon;
+ }
+
+ private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
+ {
+ _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
+
+ if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
+ {
+ titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
+ publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
+ }
+ else
+ {
+ titleName = null;
+ publisher = null;
+ }
+
+ if (string.IsNullOrWhiteSpace(titleName))
+ {
+ foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
+ {
+ if (!controlTitle.NameString.IsEmpty())
+ {
+ titleName = controlTitle.NameString.ToString();
+
+ break;
+ }
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(publisher))
+ {
+ foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
+ {
+ if (!controlTitle.PublisherString.IsEmpty())
+ {
+ publisher = controlTitle.PublisherString.ToString();
+
+ break;
+ }
+ }
+ }
+
+ if (controlData.PresenceGroupId != 0)
+ {
+ titleId = controlData.PresenceGroupId.ToString("x16");
+ }
+ else if (controlData.SaveDataOwnerId != 0)
+ {
+ titleId = controlData.SaveDataOwnerId.ToString();
+ }
+ else if (controlData.AddOnContentBaseId != 0)
+ {
+ titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
+ }
+ else
+ {
+ titleId = "0000000000000000";
+ }
+
+ version = controlData.DisplayVersionString.ToString();
+ }
+
+ private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
+ {
+ updatedControlFs = null;
+
+ string updatePath = "(unknown)";
+
+ try
+ {
+ (Nca patchNca, Nca controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
+
+ if (patchNca != null && controlNca != null)
+ {
+ updatedControlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+
+ return true;
+ }
+ }
+ catch (InvalidDataException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
+ }
+ catch (MissingKeyException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
+ }
+
+ return false;
+ }
+
+ public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
+ {
+ Nca mainNca = null;
+ Nca patchNca = null;
+ Nca controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ 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(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+ {
+ patchNca = nca;
+ }
+ else
+ {
+ mainNca = nca;
+ }
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (mainNca, patchNca, controlNca);
+ }
+
+ public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
+ {
+ Nca patchNca = null;
+ Nca controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ 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(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
+ {
+ break;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ patchNca = nca;
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (patchNca, controlNca);
+ }
+
+ public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
+ {
+ updatePath = null;
+
+ if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+ {
+ // Clear the program index part.
+ titleIdBase &= ~0xFUL;
+
+ // Load update information if exists.
+ string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+
+ if (File.Exists(titleUpdateMetadataPath))
+ {
+ updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
+
+ if (File.Exists(updatePath))
+ {
+ FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
+ PartitionFileSystem nsp = new();
+ nsp.Initialize(file.AsStorage()).ThrowIfFailure();
+
+ return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
+ }
+ }
+ }
+
+ return (null, null);
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs
new file mode 100644
index 00000000..81193c5b
--- /dev/null
+++ b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.App.Common
+{
+ public class ApplicationMetadata
+ {
+ public string Title { get; set; }
+ public bool Favorite { get; set; }
+
+ [JsonPropertyName("timespan_played")]
+ public TimeSpan TimePlayed { get; set; } = TimeSpan.Zero;
+
+ [JsonPropertyName("last_played_utc")]
+ public DateTime? LastPlayed { get; set; } = null;
+
+ [JsonPropertyName("time_played")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public double TimePlayedOld { get; set; }
+
+ [JsonPropertyName("last_played")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string LastPlayedOld { get; set; }
+
+ ///
+ /// Updates . Call this before launching a game.
+ ///
+ public void UpdatePreGame()
+ {
+ LastPlayed = DateTime.UtcNow;
+ }
+
+ ///
+ /// Updates and . Call this after a game ends.
+ ///
+ public void UpdatePostGame()
+ {
+ DateTime? prevLastPlayed = LastPlayed;
+ UpdatePreGame();
+
+ if (!prevLastPlayed.HasValue)
+ {
+ return;
+ }
+
+ TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value;
+ double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds;
+ TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero));
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs
new file mode 100644
index 00000000..a952e7ac
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Common.Utilities;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ [JsonConverter(typeof(TypedStringEnumConverter))]
+ public enum AudioBackend
+ {
+ Dummy,
+ OpenAl,
+ SoundIo,
+ SDL2,
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
new file mode 100644
index 00000000..0ee51d83
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
@@ -0,0 +1,409 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Multiplayer;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
+using Ryujinx.UI.Common.Configuration.System;
+using Ryujinx.UI.Common.Configuration.UI;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ public class ConfigurationFileFormat
+ {
+ ///
+ /// The current version of the file format
+ ///
+ public const int CurrentVersion = 48;
+
+ ///
+ /// Version of the configuration file format
+ ///
+ public int Version { get; set; }
+
+ ///
+ /// Enables or disables logging to a file on disk
+ ///
+ public bool EnableFileLog { get; set; }
+
+ ///
+ /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime.
+ ///
+ public BackendThreading BackendThreading { get; set; }
+
+ ///
+ /// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.
+ ///
+ public int ResScale { get; set; }
+
+ ///
+ /// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.
+ ///
+ public float ResScaleCustom { get; set; }
+
+ ///
+ /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
+ ///
+ public float MaxAnisotropy { get; set; }
+
+ ///
+ /// Aspect Ratio applied to the renderer window.
+ ///
+ public AspectRatio AspectRatio { get; set; }
+
+ ///
+ /// Applies anti-aliasing to the renderer.
+ ///
+ public AntiAliasing AntiAliasing { get; set; }
+
+ ///
+ /// Sets the framebuffer upscaling type.
+ ///
+ public ScalingFilter ScalingFilter { get; set; }
+
+ ///
+ /// Sets the framebuffer upscaling level.
+ ///
+ public int ScalingFilterLevel { get; set; }
+
+ ///
+ /// Dumps shaders in this local directory
+ ///
+ public string GraphicsShadersDumpPath { get; set; }
+
+ ///
+ /// Enables printing debug log messages
+ ///
+ public bool LoggingEnableDebug { get; set; }
+
+ ///
+ /// Enables printing stub log messages
+ ///
+ public bool LoggingEnableStub { get; set; }
+
+ ///
+ /// Enables printing info log messages
+ ///
+ public bool LoggingEnableInfo { get; set; }
+
+ ///
+ /// Enables printing warning log messages
+ ///
+ public bool LoggingEnableWarn { get; set; }
+
+ ///
+ /// Enables printing error log messages
+ ///
+ public bool LoggingEnableError { get; set; }
+
+ ///
+ /// Enables printing trace log messages
+ ///
+ public bool LoggingEnableTrace { get; set; }
+
+ ///
+ /// Enables printing guest log messages
+ ///
+ public bool LoggingEnableGuest { get; set; }
+
+ ///
+ /// Enables printing FS access log messages
+ ///
+ public bool LoggingEnableFsAccessLog { get; set; }
+
+ ///
+ /// Controls which log messages are written to the log targets
+ ///
+ public LogClass[] LoggingFilteredClasses { get; set; }
+
+ ///
+ /// Change Graphics API debug log level
+ ///
+ public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
+
+ ///
+ /// Change System Language
+ ///
+ public Language SystemLanguage { get; set; }
+
+ ///
+ /// Change System Region
+ ///
+ public Region SystemRegion { get; set; }
+
+ ///
+ /// Change System TimeZone
+ ///
+ public string SystemTimeZone { get; set; }
+
+ ///
+ /// Change System Time Offset in seconds
+ ///
+ public long SystemTimeOffset { get; set; }
+
+ ///
+ /// Enables or disables Docked Mode
+ ///
+ public bool DockedMode { get; set; }
+
+ ///
+ /// Enables or disables Discord Rich Presence
+ ///
+ public bool EnableDiscordIntegration { get; set; }
+
+ ///
+ /// Checks for updates when Ryujinx starts when enabled
+ ///
+ public bool CheckUpdatesOnStart { get; set; }
+
+ ///
+ /// Show "Confirm Exit" Dialog
+ ///
+ public bool ShowConfirmExit { get; set; }
+
+ ///
+ /// Whether to hide cursor on idle, always or never
+ ///
+ public HideCursorMode HideCursor { get; set; }
+
+ ///
+ /// Enables or disables Vertical Sync
+ ///
+ public bool EnableVsync { get; set; }
+
+ ///
+ /// Enables or disables Shader cache
+ ///
+ public bool EnableShaderCache { get; set; }
+
+ ///
+ /// Enables or disables texture recompression
+ ///
+ public bool EnableTextureRecompression { get; set; }
+
+ ///
+ /// Enables or disables Macro high-level emulation
+ ///
+ public bool EnableMacroHLE { get; set; }
+
+ ///
+ /// Enables or disables color space passthrough, if available.
+ ///
+ public bool EnableColorSpacePassthrough { get; set; }
+
+ ///
+ /// Enables or disables profiled translation cache persistency
+ ///
+ public bool EnablePtc { get; set; }
+
+ ///
+ /// Enables or disables guest Internet access
+ ///
+ public bool EnableInternetAccess { get; set; }
+
+ ///
+ /// Enables integrity checks on Game content files
+ ///
+ public bool EnableFsIntegrityChecks { get; set; }
+
+ ///
+ /// Enables FS access log output to the console. Possible modes are 0-3
+ ///
+ public int FsGlobalAccessLogMode { get; set; }
+
+ ///
+ /// The selected audio backend
+ ///
+ public AudioBackend AudioBackend { get; set; }
+
+ ///
+ /// The audio volume
+ ///
+ public float AudioVolume { get; set; }
+
+ ///
+ /// The selected memory manager mode
+ ///
+ public MemoryManagerMode MemoryManagerMode { get; set; }
+
+ ///
+ /// Expands the RAM amount on the emulated system from 4GiB to 6GiB
+ ///
+ public bool ExpandRam { get; set; }
+
+ ///
+ /// Enable or disable ignoring missing services
+ ///
+ public bool IgnoreMissingServices { get; set; }
+
+ ///
+ /// Used to toggle columns in the GUI
+ ///
+ public GuiColumns GuiColumns { get; set; }
+
+ ///
+ /// Used to configure column sort settings in the GUI
+ ///
+ public ColumnSort ColumnSort { get; set; }
+
+ ///
+ /// A list of directories containing games to be used to load games into the games list
+ ///
+ public List GameDirs { get; set; }
+
+ ///
+ /// A list of file types to be hidden in the games List
+ ///
+ public ShownFileTypes ShownFileTypes { get; set; }
+
+ ///
+ /// Main window start-up position, size and state
+ ///
+ public WindowStartup WindowStartup { get; set; }
+
+ ///
+ /// Language Code for the UI
+ ///
+ public string LanguageCode { get; set; }
+
+ ///
+ /// Enable or disable custom themes in the GUI
+ ///
+ public bool EnableCustomTheme { get; set; }
+
+ ///
+ /// Path to custom GUI theme
+ ///
+ public string CustomThemePath { get; set; }
+
+ ///
+ /// Chooses the base style // Not Used
+ ///
+ public string BaseStyle { get; set; }
+
+ ///
+ /// Chooses the view mode of the game list // Not Used
+ ///
+ public int GameListViewMode { get; set; }
+
+ ///
+ /// Show application name in Grid Mode // Not Used
+ ///
+ public bool ShowNames { get; set; }
+
+ ///
+ /// Sets App Icon Size // Not Used
+ ///
+ public int GridSize { get; set; }
+
+ ///
+ /// Sorts Apps in the game list // Not Used
+ ///
+ public int ApplicationSort { get; set; }
+
+ ///
+ /// Sets if Grid is ordered in Ascending Order // Not Used
+ ///
+ public bool IsAscendingOrder { get; set; }
+
+ ///
+ /// Start games in fullscreen mode
+ ///
+ public bool StartFullscreen { get; set; }
+
+ ///
+ /// Show console window
+ ///
+ public bool ShowConsole { get; set; }
+
+ ///
+ /// Enable or disable keyboard support (Independent from controllers binding)
+ ///
+ public bool EnableKeyboard { get; set; }
+
+ ///
+ /// Enable or disable mouse support (Independent from controllers binding)
+ ///
+ public bool EnableMouse { get; set; }
+
+ ///
+ /// Hotkey Keyboard Bindings
+ ///
+ public KeyboardHotkeys Hotkeys { get; set; }
+
+ ///
+ /// Legacy keyboard control bindings
+ ///
+ /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)
+ /// TODO: Remove this when those older versions aren't in use anymore.
+ public List KeyboardConfig { get; set; }
+
+ ///
+ /// Legacy controller control bindings
+ ///
+ /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)
+ /// TODO: Remove this when those older versions aren't in use anymore.
+ public List ControllerConfig { get; set; }
+
+ ///
+ /// Input configurations
+ ///
+ public List InputConfig { get; set; }
+
+ ///
+ /// Graphics backend
+ ///
+ public GraphicsBackend GraphicsBackend { get; set; }
+
+ ///
+ /// Preferred GPU
+ ///
+ public string PreferredGpu { get; set; }
+
+ ///
+ /// Multiplayer Mode
+ ///
+ public MultiplayerMode MultiplayerMode { get; set; }
+
+ ///
+ /// GUID for the network interface used by LAN (or 0 for default)
+ ///
+ public string MultiplayerLanInterfaceId { get; set; }
+
+ ///
+ /// Uses Hypervisor over JIT if available
+ ///
+ public bool UseHypervisor { get; set; }
+
+ ///
+ /// Loads a configuration file from disk
+ ///
+ /// The path to the JSON configuration file
+ /// Parsed configuration file
+ public static bool TryLoad(string path, out ConfigurationFileFormat configurationFileFormat)
+ {
+ try
+ {
+ configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
+
+ return configurationFileFormat.Version != 0;
+ }
+ catch
+ {
+ configurationFileFormat = null;
+
+ return false;
+ }
+ }
+
+ ///
+ /// Save a configuration file to disk
+ ///
+ /// The path to the JSON configuration file
+ public void SaveConfig(string path)
+ {
+ JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs
new file mode 100644
index 00000000..9861ebf1
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs
@@ -0,0 +1,9 @@
+using Ryujinx.Common.Utilities;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ internal static class ConfigurationFileFormatSettings
+ {
+ public static readonly ConfigurationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs
new file mode 100644
index 00000000..3c3e3f20
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ [JsonSourceGenerationOptions(WriteIndented = true)]
+ [JsonSerializable(typeof(ConfigurationFileFormat))]
+ internal partial class ConfigurationJsonSerializerContext : JsonSerializerContext
+ {
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
new file mode 100644
index 00000000..1d6934ce
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
@@ -0,0 +1,1562 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using Ryujinx.Common.Configuration.Multiplayer;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Vulkan;
+using Ryujinx.UI.Common.Configuration.System;
+using Ryujinx.UI.Common.Configuration.UI;
+using Ryujinx.UI.Common.Helper;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ public class ConfigurationState
+ {
+ ///
+ /// UI configuration section
+ ///
+ public class UISection
+ {
+ public class Columns
+ {
+ public ReactiveObject FavColumn { get; private set; }
+ public ReactiveObject IconColumn { get; private set; }
+ public ReactiveObject AppColumn { get; private set; }
+ public ReactiveObject DevColumn { get; private set; }
+ public ReactiveObject VersionColumn { get; private set; }
+ public ReactiveObject TimePlayedColumn { get; private set; }
+ public ReactiveObject LastPlayedColumn { get; private set; }
+ public ReactiveObject FileExtColumn { get; private set; }
+ public ReactiveObject FileSizeColumn { get; private set; }
+ public ReactiveObject PathColumn { get; private set; }
+
+ public Columns()
+ {
+ FavColumn = new ReactiveObject();
+ IconColumn = new ReactiveObject();
+ AppColumn = new ReactiveObject();
+ DevColumn = new ReactiveObject();
+ VersionColumn = new ReactiveObject();
+ TimePlayedColumn = new ReactiveObject();
+ LastPlayedColumn = new ReactiveObject();
+ FileExtColumn = new ReactiveObject();
+ FileSizeColumn = new ReactiveObject();
+ PathColumn = new ReactiveObject();
+ }
+ }
+
+ public class ColumnSortSettings
+ {
+ public ReactiveObject SortColumnId { get; private set; }
+ public ReactiveObject SortAscending { get; private set; }
+
+ public ColumnSortSettings()
+ {
+ SortColumnId = new ReactiveObject();
+ SortAscending = new ReactiveObject();
+ }
+ }
+
+ ///
+ /// Used to toggle which file types are shown in the UI
+ ///
+ public class ShownFileTypeSettings
+ {
+ public ReactiveObject NSP { get; private set; }
+ public ReactiveObject PFS0 { get; private set; }
+ public ReactiveObject XCI { get; private set; }
+ public ReactiveObject NCA { get; private set; }
+ public ReactiveObject NRO { get; private set; }
+ public ReactiveObject NSO { get; private set; }
+
+ public ShownFileTypeSettings()
+ {
+ NSP = new ReactiveObject();
+ PFS0 = new ReactiveObject();
+ XCI = new ReactiveObject();
+ NCA = new ReactiveObject();
+ NRO = new ReactiveObject();
+ NSO = new ReactiveObject();
+ }
+ }
+
+ //
+ /// Determines main window start-up position, size and state
+ ///
+ public class WindowStartupSettings
+ {
+ public ReactiveObject WindowSizeWidth { get; private set; }
+ public ReactiveObject WindowSizeHeight { get; private set; }
+ public ReactiveObject WindowPositionX { get; private set; }
+ public ReactiveObject WindowPositionY { get; private set; }
+ public ReactiveObject WindowMaximized { get; private set; }
+
+ public WindowStartupSettings()
+ {
+ WindowSizeWidth = new ReactiveObject();
+ WindowSizeHeight = new ReactiveObject();
+ WindowPositionX = new ReactiveObject();
+ WindowPositionY = new ReactiveObject();
+ WindowMaximized = new ReactiveObject();
+ }
+ }
+
+ ///
+ /// Used to toggle columns in the GUI
+ ///
+ public Columns GuiColumns { get; private set; }
+
+ ///
+ /// Used to configure column sort settings in the GUI
+ ///
+ public ColumnSortSettings ColumnSort { get; private set; }
+
+ ///
+ /// A list of directories containing games to be used to load games into the games list
+ ///
+ public ReactiveObject> GameDirs { get; private set; }
+
+ ///
+ /// A list of file types to be hidden in the games List
+ ///
+ public ShownFileTypeSettings ShownFileTypes { get; private set; }
+
+ ///
+ /// Determines main window start-up position, size and state
+ ///
+ public WindowStartupSettings WindowStartup { get; private set; }
+
+ ///
+ /// Language Code for the UI
+ ///
+ public ReactiveObject LanguageCode { get; private set; }
+
+ ///
+ /// Enable or disable custom themes in the GUI
+ ///
+ public ReactiveObject EnableCustomTheme { get; private set; }
+
+ ///
+ /// Path to custom GUI theme
+ ///
+ public ReactiveObject CustomThemePath { get; private set; }
+
+ ///
+ /// Selects the base style
+ ///
+ public ReactiveObject BaseStyle { get; private set; }
+
+ ///
+ /// Start games in fullscreen mode
+ ///
+ public ReactiveObject StartFullscreen { get; private set; }
+
+ ///
+ /// Hide / Show Console Window
+ ///
+ public ReactiveObject ShowConsole { get; private set; }
+
+ ///
+ /// View Mode of the Game list
+ ///
+ public ReactiveObject GameListViewMode { get; private set; }
+
+ ///
+ /// Show application name in Grid Mode
+ ///
+ public ReactiveObject ShowNames { get; private set; }
+
+ ///
+ /// Sets App Icon Size in Grid Mode
+ ///
+ public ReactiveObject GridSize { get; private set; }
+
+ ///
+ /// Sorts Apps in Grid Mode
+ ///
+ public ReactiveObject ApplicationSort { get; private set; }
+
+ ///
+ /// Sets if Grid is ordered in Ascending Order
+ ///
+ public ReactiveObject IsAscendingOrder { get; private set; }
+
+ public UISection()
+ {
+ GuiColumns = new Columns();
+ ColumnSort = new ColumnSortSettings();
+ GameDirs = new ReactiveObject>();
+ ShownFileTypes = new ShownFileTypeSettings();
+ WindowStartup = new WindowStartupSettings();
+ EnableCustomTheme = new ReactiveObject();
+ CustomThemePath = new ReactiveObject();
+ BaseStyle = new ReactiveObject();
+ StartFullscreen = new ReactiveObject();
+ GameListViewMode = new ReactiveObject();
+ ShowNames = new ReactiveObject();
+ GridSize = new ReactiveObject();
+ ApplicationSort = new ReactiveObject();
+ IsAscendingOrder = new ReactiveObject();
+ LanguageCode = new ReactiveObject();
+ ShowConsole = new ReactiveObject();
+ ShowConsole.Event += static (s, e) => { ConsoleHelper.SetConsoleWindowState(e.NewValue); };
+ }
+ }
+
+ ///
+ /// Logger configuration section
+ ///
+ public class LoggerSection
+ {
+ ///
+ /// Enables printing debug log messages
+ ///
+ public ReactiveObject EnableDebug { get; private set; }
+
+ ///
+ /// Enables printing stub log messages
+ ///
+ public ReactiveObject EnableStub { get; private set; }
+
+ ///
+ /// Enables printing info log messages
+ ///
+ public ReactiveObject EnableInfo { get; private set; }
+
+ ///
+ /// Enables printing warning log messages
+ ///
+ public ReactiveObject EnableWarn { get; private set; }
+
+ ///
+ /// Enables printing error log messages
+ ///
+ public ReactiveObject EnableError { get; private set; }
+
+ ///
+ /// Enables printing trace log messages
+ ///
+ public ReactiveObject EnableTrace { get; private set; }
+
+ ///
+ /// Enables printing guest log messages
+ ///
+ public ReactiveObject EnableGuest { get; private set; }
+
+ ///
+ /// Enables printing FS access log messages
+ ///
+ public ReactiveObject EnableFsAccessLog { get; private set; }
+
+ ///
+ /// Controls which log messages are written to the log targets
+ ///
+ public ReactiveObject FilteredClasses { get; private set; }
+
+ ///
+ /// Enables or disables logging to a file on disk
+ ///
+ public ReactiveObject EnableFileLog { get; private set; }
+
+ ///
+ /// Controls which OpenGL log messages are recorded in the log
+ ///
+ public ReactiveObject GraphicsDebugLevel { get; private set; }
+
+ public LoggerSection()
+ {
+ EnableDebug = new ReactiveObject();
+ EnableStub = new ReactiveObject();
+ EnableInfo = new ReactiveObject();
+ EnableWarn = new ReactiveObject();
+ EnableError = new ReactiveObject();
+ EnableTrace = new ReactiveObject();
+ EnableGuest = new ReactiveObject();
+ EnableFsAccessLog = new ReactiveObject();
+ FilteredClasses = new ReactiveObject();
+ EnableFileLog = new ReactiveObject();
+ EnableFileLog.Event += static (sender, e) => LogValueChange(e, nameof(EnableFileLog));
+ GraphicsDebugLevel = new ReactiveObject();
+ }
+ }
+
+ ///
+ /// System configuration section
+ ///
+ public class SystemSection
+ {
+ ///
+ /// Change System Language
+ ///
+ public ReactiveObject Language { get; private set; }
+
+ ///
+ /// Change System Region
+ ///
+ public ReactiveObject Region { get; private set; }
+
+ ///
+ /// Change System TimeZone
+ ///
+ public ReactiveObject TimeZone { get; private set; }
+
+ ///
+ /// System Time Offset in Seconds
+ ///
+ public ReactiveObject SystemTimeOffset { get; private set; }
+
+ ///
+ /// Enables or disables Docked Mode
+ ///
+ public ReactiveObject EnableDockedMode { get; private set; }
+
+ ///
+ /// Enables or disables profiled translation cache persistency
+ ///
+ public ReactiveObject EnablePtc { get; private set; }
+
+ ///
+ /// Enables or disables guest Internet access
+ ///
+ public ReactiveObject EnableInternetAccess { get; private set; }
+
+ ///
+ /// Enables integrity checks on Game content files
+ ///
+ public ReactiveObject EnableFsIntegrityChecks { get; private set; }
+
+ ///
+ /// Enables FS access log output to the console. Possible modes are 0-3
+ ///
+ public ReactiveObject FsGlobalAccessLogMode { get; private set; }
+
+ ///
+ /// The selected audio backend
+ ///
+ public ReactiveObject AudioBackend { get; private set; }
+
+ ///
+ /// The audio backend volume
+ ///
+ public ReactiveObject AudioVolume { get; private set; }
+
+ ///
+ /// The selected memory manager mode
+ ///
+ public ReactiveObject MemoryManagerMode { get; private set; }
+
+ ///
+ /// Defines the amount of RAM available on the emulated system, and how it is distributed
+ ///
+ public ReactiveObject ExpandRam { get; private set; }
+
+ ///
+ /// Enable or disable ignoring missing services
+ ///
+ public ReactiveObject IgnoreMissingServices { get; private set; }
+
+ ///
+ /// Uses Hypervisor over JIT if available
+ ///
+ public ReactiveObject UseHypervisor { get; private set; }
+
+ public SystemSection()
+ {
+ Language = new ReactiveObject();
+ Region = new ReactiveObject();
+ TimeZone = new ReactiveObject();
+ SystemTimeOffset = new ReactiveObject();
+ EnableDockedMode = new ReactiveObject();
+ EnableDockedMode.Event += static (sender, e) => LogValueChange(e, nameof(EnableDockedMode));
+ EnablePtc = new ReactiveObject();
+ EnablePtc.Event += static (sender, e) => LogValueChange(e, nameof(EnablePtc));
+ EnableInternetAccess = new ReactiveObject();
+ EnableInternetAccess.Event += static (sender, e) => LogValueChange(e, nameof(EnableInternetAccess));
+ EnableFsIntegrityChecks = new ReactiveObject();
+ EnableFsIntegrityChecks.Event += static (sender, e) => LogValueChange(e, nameof(EnableFsIntegrityChecks));
+ FsGlobalAccessLogMode = new ReactiveObject();
+ FsGlobalAccessLogMode.Event += static (sender, e) => LogValueChange(e, nameof(FsGlobalAccessLogMode));
+ AudioBackend = new ReactiveObject();
+ AudioBackend.Event += static (sender, e) => LogValueChange(e, nameof(AudioBackend));
+ MemoryManagerMode = new ReactiveObject();
+ MemoryManagerMode.Event += static (sender, e) => LogValueChange(e, nameof(MemoryManagerMode));
+ ExpandRam = new ReactiveObject();
+ ExpandRam.Event += static (sender, e) => LogValueChange(e, nameof(ExpandRam));
+ IgnoreMissingServices = new ReactiveObject();
+ IgnoreMissingServices.Event += static (sender, e) => LogValueChange(e, nameof(IgnoreMissingServices));
+ AudioVolume = new ReactiveObject();
+ AudioVolume.Event += static (sender, e) => LogValueChange(e, nameof(AudioVolume));
+ UseHypervisor = new ReactiveObject();
+ UseHypervisor.Event += static (sender, e) => LogValueChange(e, nameof(UseHypervisor));
+ }
+ }
+
+ ///
+ /// Hid configuration section
+ ///
+ public class HidSection
+ {
+ ///
+ /// Enable or disable keyboard support (Independent from controllers binding)
+ ///
+ public ReactiveObject EnableKeyboard { get; private set; }
+
+ ///
+ /// Enable or disable mouse support (Independent from controllers binding)
+ ///
+ public ReactiveObject EnableMouse { get; private set; }
+
+ ///
+ /// Hotkey Keyboard Bindings
+ ///
+ public ReactiveObject Hotkeys { get; private set; }
+
+ ///
+ /// Input device configuration.
+ /// NOTE: This ReactiveObject won't issue an event when the List has elements added or removed.
+ /// TODO: Implement a ReactiveList class.
+ ///
+ public ReactiveObject> InputConfig { get; private set; }
+
+ public HidSection()
+ {
+ EnableKeyboard = new ReactiveObject();
+ EnableMouse = new ReactiveObject();
+ Hotkeys = new ReactiveObject();
+ InputConfig = new ReactiveObject>();
+ }
+ }
+
+ ///
+ /// Graphics configuration section
+ ///
+ public class GraphicsSection
+ {
+ ///
+ /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime.
+ ///
+ public ReactiveObject BackendThreading { get; private set; }
+
+ ///
+ /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
+ ///
+ public ReactiveObject MaxAnisotropy { get; private set; }
+
+ ///
+ /// Aspect Ratio applied to the renderer window.
+ ///
+ public ReactiveObject AspectRatio { get; private set; }
+
+ ///
+ /// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.
+ ///
+ public ReactiveObject ResScale { get; private set; }
+
+ ///
+ /// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.
+ ///
+ public ReactiveObject ResScaleCustom { get; private set; }
+
+ ///
+ /// Dumps shaders in this local directory
+ ///
+ public ReactiveObject ShadersDumpPath { get; private set; }
+
+ ///
+ /// Enables or disables Vertical Sync
+ ///
+ public ReactiveObject EnableVsync { get; private set; }
+
+ ///
+ /// Enables or disables Shader cache
+ ///
+ public ReactiveObject EnableShaderCache { get; private set; }
+
+ ///
+ /// Enables or disables texture recompression
+ ///
+ public ReactiveObject EnableTextureRecompression { get; private set; }
+
+ ///
+ /// Enables or disables Macro high-level emulation
+ ///
+ public ReactiveObject EnableMacroHLE { get; private set; }
+
+ ///
+ /// Enables or disables color space passthrough, if available.
+ ///
+ public ReactiveObject EnableColorSpacePassthrough { get; private set; }
+
+ ///
+ /// Graphics backend
+ ///
+ public ReactiveObject GraphicsBackend { get; private set; }
+
+ ///
+ /// Applies anti-aliasing to the renderer.
+ ///
+ public ReactiveObject AntiAliasing { get; private set; }
+
+ ///
+ /// Sets the framebuffer upscaling type.
+ ///
+ public ReactiveObject ScalingFilter { get; private set; }
+
+ ///
+ /// Sets the framebuffer upscaling level.
+ ///
+ public ReactiveObject ScalingFilterLevel { get; private set; }
+
+ ///
+ /// Preferred GPU
+ ///
+ public ReactiveObject PreferredGpu { get; private set; }
+
+ public GraphicsSection()
+ {
+ BackendThreading = new ReactiveObject();
+ BackendThreading.Event += static (sender, e) => LogValueChange(e, nameof(BackendThreading));
+ ResScale = new ReactiveObject();
+ ResScale.Event += static (sender, e) => LogValueChange(e, nameof(ResScale));
+ ResScaleCustom = new ReactiveObject();
+ ResScaleCustom.Event += static (sender, e) => LogValueChange(e, nameof(ResScaleCustom));
+ MaxAnisotropy = new ReactiveObject();
+ MaxAnisotropy.Event += static (sender, e) => LogValueChange(e, nameof(MaxAnisotropy));
+ AspectRatio = new ReactiveObject();
+ AspectRatio.Event += static (sender, e) => LogValueChange(e, nameof(AspectRatio));
+ ShadersDumpPath = new ReactiveObject();
+ EnableVsync = new ReactiveObject();
+ EnableVsync.Event += static (sender, e) => LogValueChange(e, nameof(EnableVsync));
+ EnableShaderCache = new ReactiveObject();
+ EnableShaderCache.Event += static (sender, e) => LogValueChange(e, nameof(EnableShaderCache));
+ EnableTextureRecompression = new ReactiveObject();
+ EnableTextureRecompression.Event += static (sender, e) => LogValueChange(e, nameof(EnableTextureRecompression));
+ GraphicsBackend = new ReactiveObject();
+ GraphicsBackend.Event += static (sender, e) => LogValueChange(e, nameof(GraphicsBackend));
+ PreferredGpu = new ReactiveObject();
+ PreferredGpu.Event += static (sender, e) => LogValueChange(e, nameof(PreferredGpu));
+ EnableMacroHLE = new ReactiveObject();
+ EnableMacroHLE.Event += static (sender, e) => LogValueChange(e, nameof(EnableMacroHLE));
+ EnableColorSpacePassthrough = new ReactiveObject();
+ EnableColorSpacePassthrough.Event += static (sender, e) => LogValueChange(e, nameof(EnableColorSpacePassthrough));
+ AntiAliasing = new ReactiveObject();
+ AntiAliasing.Event += static (sender, e) => LogValueChange(e, nameof(AntiAliasing));
+ ScalingFilter = new ReactiveObject();
+ ScalingFilter.Event += static (sender, e) => LogValueChange(e, nameof(ScalingFilter));
+ ScalingFilterLevel = new ReactiveObject();
+ ScalingFilterLevel.Event += static (sender, e) => LogValueChange(e, nameof(ScalingFilterLevel));
+ }
+ }
+
+ ///
+ /// Multiplayer configuration section
+ ///
+ public class MultiplayerSection
+ {
+ ///
+ /// GUID for the network interface used by LAN (or 0 for default)
+ ///
+ public ReactiveObject LanInterfaceId { get; private set; }
+
+ ///
+ /// Multiplayer Mode
+ ///
+ public ReactiveObject Mode { get; private set; }
+
+ public MultiplayerSection()
+ {
+ LanInterfaceId = new ReactiveObject();
+ Mode = new ReactiveObject();
+ Mode.Event += static (_, e) => LogValueChange(e, nameof(MultiplayerMode));
+ }
+ }
+
+ ///
+ /// The default configuration instance
+ ///
+ public static ConfigurationState Instance { get; private set; }
+
+ ///
+ /// The UI section
+ ///
+ public UISection UI { get; private set; }
+
+ ///
+ /// The Logger section
+ ///
+ public LoggerSection Logger { get; private set; }
+
+ ///
+ /// The System section
+ ///
+ public SystemSection System { get; private set; }
+
+ ///
+ /// The Graphics section
+ ///
+ public GraphicsSection Graphics { get; private set; }
+
+ ///
+ /// The Hid section
+ ///
+ public HidSection Hid { get; private set; }
+
+ ///
+ /// The Multiplayer section
+ ///
+ public MultiplayerSection Multiplayer { get; private set; }
+
+ ///
+ /// Enables or disables Discord Rich Presence
+ ///
+ public ReactiveObject EnableDiscordIntegration { get; private set; }
+
+ ///
+ /// Checks for updates when Ryujinx starts when enabled
+ ///
+ public ReactiveObject CheckUpdatesOnStart { get; private set; }
+
+ ///
+ /// Show "Confirm Exit" Dialog
+ ///
+ public ReactiveObject ShowConfirmExit { get; private set; }
+
+ ///
+ /// Hide Cursor on Idle
+ ///
+ public ReactiveObject HideCursor { get; private set; }
+
+ private ConfigurationState()
+ {
+ UI = new UISection();
+ Logger = new LoggerSection();
+ System = new SystemSection();
+ Graphics = new GraphicsSection();
+ Hid = new HidSection();
+ Multiplayer = new MultiplayerSection();
+ EnableDiscordIntegration = new ReactiveObject();
+ CheckUpdatesOnStart = new ReactiveObject();
+ ShowConfirmExit = new ReactiveObject();
+ HideCursor = new ReactiveObject();
+ }
+
+ public ConfigurationFileFormat ToFileFormat()
+ {
+ ConfigurationFileFormat configurationFile = new()
+ {
+ Version = ConfigurationFileFormat.CurrentVersion,
+ BackendThreading = Graphics.BackendThreading,
+ EnableFileLog = Logger.EnableFileLog,
+ ResScale = Graphics.ResScale,
+ ResScaleCustom = Graphics.ResScaleCustom,
+ MaxAnisotropy = Graphics.MaxAnisotropy,
+ AspectRatio = Graphics.AspectRatio,
+ AntiAliasing = Graphics.AntiAliasing,
+ ScalingFilter = Graphics.ScalingFilter,
+ ScalingFilterLevel = Graphics.ScalingFilterLevel,
+ GraphicsShadersDumpPath = Graphics.ShadersDumpPath,
+ LoggingEnableDebug = Logger.EnableDebug,
+ LoggingEnableStub = Logger.EnableStub,
+ LoggingEnableInfo = Logger.EnableInfo,
+ LoggingEnableWarn = Logger.EnableWarn,
+ LoggingEnableError = Logger.EnableError,
+ LoggingEnableTrace = Logger.EnableTrace,
+ LoggingEnableGuest = Logger.EnableGuest,
+ LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
+ LoggingFilteredClasses = Logger.FilteredClasses,
+ LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
+ SystemLanguage = System.Language,
+ SystemRegion = System.Region,
+ SystemTimeZone = System.TimeZone,
+ SystemTimeOffset = System.SystemTimeOffset,
+ DockedMode = System.EnableDockedMode,
+ EnableDiscordIntegration = EnableDiscordIntegration,
+ CheckUpdatesOnStart = CheckUpdatesOnStart,
+ ShowConfirmExit = ShowConfirmExit,
+ HideCursor = HideCursor,
+ EnableVsync = Graphics.EnableVsync,
+ EnableShaderCache = Graphics.EnableShaderCache,
+ EnableTextureRecompression = Graphics.EnableTextureRecompression,
+ EnableMacroHLE = Graphics.EnableMacroHLE,
+ EnableColorSpacePassthrough = Graphics.EnableColorSpacePassthrough,
+ EnablePtc = System.EnablePtc,
+ EnableInternetAccess = System.EnableInternetAccess,
+ EnableFsIntegrityChecks = System.EnableFsIntegrityChecks,
+ FsGlobalAccessLogMode = System.FsGlobalAccessLogMode,
+ AudioBackend = System.AudioBackend,
+ AudioVolume = System.AudioVolume,
+ MemoryManagerMode = System.MemoryManagerMode,
+ ExpandRam = System.ExpandRam,
+ IgnoreMissingServices = System.IgnoreMissingServices,
+ UseHypervisor = System.UseHypervisor,
+ GuiColumns = new GuiColumns
+ {
+ FavColumn = UI.GuiColumns.FavColumn,
+ IconColumn = UI.GuiColumns.IconColumn,
+ AppColumn = UI.GuiColumns.AppColumn,
+ DevColumn = UI.GuiColumns.DevColumn,
+ VersionColumn = UI.GuiColumns.VersionColumn,
+ TimePlayedColumn = UI.GuiColumns.TimePlayedColumn,
+ LastPlayedColumn = UI.GuiColumns.LastPlayedColumn,
+ FileExtColumn = UI.GuiColumns.FileExtColumn,
+ FileSizeColumn = UI.GuiColumns.FileSizeColumn,
+ PathColumn = UI.GuiColumns.PathColumn,
+ },
+ ColumnSort = new ColumnSort
+ {
+ SortColumnId = UI.ColumnSort.SortColumnId,
+ SortAscending = UI.ColumnSort.SortAscending,
+ },
+ GameDirs = UI.GameDirs,
+ ShownFileTypes = new ShownFileTypes
+ {
+ NSP = UI.ShownFileTypes.NSP,
+ PFS0 = UI.ShownFileTypes.PFS0,
+ XCI = UI.ShownFileTypes.XCI,
+ NCA = UI.ShownFileTypes.NCA,
+ NRO = UI.ShownFileTypes.NRO,
+ NSO = UI.ShownFileTypes.NSO,
+ },
+ WindowStartup = new WindowStartup
+ {
+ WindowSizeWidth = UI.WindowStartup.WindowSizeWidth,
+ WindowSizeHeight = UI.WindowStartup.WindowSizeHeight,
+ WindowPositionX = UI.WindowStartup.WindowPositionX,
+ WindowPositionY = UI.WindowStartup.WindowPositionY,
+ WindowMaximized = UI.WindowStartup.WindowMaximized,
+ },
+ LanguageCode = UI.LanguageCode,
+ EnableCustomTheme = UI.EnableCustomTheme,
+ CustomThemePath = UI.CustomThemePath,
+ BaseStyle = UI.BaseStyle,
+ GameListViewMode = UI.GameListViewMode,
+ ShowNames = UI.ShowNames,
+ GridSize = UI.GridSize,
+ ApplicationSort = UI.ApplicationSort,
+ IsAscendingOrder = UI.IsAscendingOrder,
+ StartFullscreen = UI.StartFullscreen,
+ ShowConsole = UI.ShowConsole,
+ EnableKeyboard = Hid.EnableKeyboard,
+ EnableMouse = Hid.EnableMouse,
+ Hotkeys = Hid.Hotkeys,
+ KeyboardConfig = new List(),
+ ControllerConfig = new List(),
+ InputConfig = Hid.InputConfig,
+ GraphicsBackend = Graphics.GraphicsBackend,
+ PreferredGpu = Graphics.PreferredGpu,
+ MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
+ MultiplayerMode = Multiplayer.Mode,
+ };
+
+ return configurationFile;
+ }
+
+ public void LoadDefault()
+ {
+ Logger.EnableFileLog.Value = true;
+ Graphics.BackendThreading.Value = BackendThreading.Auto;
+ Graphics.ResScale.Value = 1;
+ Graphics.ResScaleCustom.Value = 1.0f;
+ Graphics.MaxAnisotropy.Value = -1.0f;
+ Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
+ Graphics.GraphicsBackend.Value = DefaultGraphicsBackend();
+ Graphics.PreferredGpu.Value = "";
+ Graphics.ShadersDumpPath.Value = "";
+ Logger.EnableDebug.Value = false;
+ Logger.EnableStub.Value = true;
+ Logger.EnableInfo.Value = true;
+ Logger.EnableWarn.Value = true;
+ Logger.EnableError.Value = true;
+ Logger.EnableTrace.Value = false;
+ Logger.EnableGuest.Value = true;
+ Logger.EnableFsAccessLog.Value = false;
+ Logger.FilteredClasses.Value = Array.Empty();
+ Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
+ System.Language.Value = Language.AmericanEnglish;
+ System.Region.Value = Region.USA;
+ System.TimeZone.Value = "UTC";
+ System.SystemTimeOffset.Value = 0;
+ System.EnableDockedMode.Value = true;
+ EnableDiscordIntegration.Value = true;
+ CheckUpdatesOnStart.Value = true;
+ ShowConfirmExit.Value = true;
+ HideCursor.Value = HideCursorMode.OnIdle;
+ Graphics.EnableVsync.Value = true;
+ Graphics.EnableShaderCache.Value = true;
+ Graphics.EnableTextureRecompression.Value = false;
+ Graphics.EnableMacroHLE.Value = true;
+ Graphics.EnableColorSpacePassthrough.Value = false;
+ Graphics.AntiAliasing.Value = AntiAliasing.None;
+ Graphics.ScalingFilter.Value = ScalingFilter.Bilinear;
+ Graphics.ScalingFilterLevel.Value = 80;
+ System.EnablePtc.Value = true;
+ System.EnableInternetAccess.Value = false;
+ System.EnableFsIntegrityChecks.Value = true;
+ System.FsGlobalAccessLogMode.Value = 0;
+ System.AudioBackend.Value = AudioBackend.SDL2;
+ System.AudioVolume.Value = 1;
+ System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
+ System.ExpandRam.Value = false;
+ System.IgnoreMissingServices.Value = false;
+ System.UseHypervisor.Value = true;
+ Multiplayer.LanInterfaceId.Value = "0";
+ Multiplayer.Mode.Value = MultiplayerMode.Disabled;
+ UI.GuiColumns.FavColumn.Value = true;
+ UI.GuiColumns.IconColumn.Value = true;
+ UI.GuiColumns.AppColumn.Value = true;
+ UI.GuiColumns.DevColumn.Value = true;
+ UI.GuiColumns.VersionColumn.Value = true;
+ UI.GuiColumns.TimePlayedColumn.Value = true;
+ UI.GuiColumns.LastPlayedColumn.Value = true;
+ UI.GuiColumns.FileExtColumn.Value = true;
+ UI.GuiColumns.FileSizeColumn.Value = true;
+ UI.GuiColumns.PathColumn.Value = true;
+ UI.ColumnSort.SortColumnId.Value = 0;
+ UI.ColumnSort.SortAscending.Value = false;
+ UI.GameDirs.Value = new List();
+ UI.ShownFileTypes.NSP.Value = true;
+ UI.ShownFileTypes.PFS0.Value = true;
+ UI.ShownFileTypes.XCI.Value = true;
+ UI.ShownFileTypes.NCA.Value = true;
+ UI.ShownFileTypes.NRO.Value = true;
+ UI.ShownFileTypes.NSO.Value = true;
+ UI.EnableCustomTheme.Value = true;
+ UI.LanguageCode.Value = "en_US";
+ UI.CustomThemePath.Value = "";
+ UI.BaseStyle.Value = "Dark";
+ UI.GameListViewMode.Value = 0;
+ UI.ShowNames.Value = true;
+ UI.GridSize.Value = 2;
+ UI.ApplicationSort.Value = 0;
+ UI.IsAscendingOrder.Value = true;
+ UI.StartFullscreen.Value = false;
+ UI.ShowConsole.Value = true;
+ UI.WindowStartup.WindowSizeWidth.Value = 1280;
+ UI.WindowStartup.WindowSizeHeight.Value = 760;
+ UI.WindowStartup.WindowPositionX.Value = 0;
+ UI.WindowStartup.WindowPositionY.Value = 0;
+ UI.WindowStartup.WindowMaximized.Value = false;
+ Hid.EnableKeyboard.Value = false;
+ Hid.EnableMouse.Value = false;
+ Hid.Hotkeys.Value = new KeyboardHotkeys
+ {
+ ToggleVsync = Key.F1,
+ ToggleMute = Key.F2,
+ Screenshot = Key.F8,
+ ShowUI = Key.F4,
+ Pause = Key.F5,
+ ResScaleUp = Key.Unbound,
+ ResScaleDown = Key.Unbound,
+ VolumeUp = Key.Unbound,
+ VolumeDown = Key.Unbound,
+ };
+ Hid.InputConfig.Value = new List
+ {
+ new StandardKeyboardInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = "0",
+ PlayerIndex = PlayerIndex.Player1,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig
+ {
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound,
+ },
+ LeftJoyconStick = new JoyconConfigKeyboardStick
+ {
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
+ },
+ RightJoycon = new RightJoyconCommonConfig
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound,
+ },
+ RightJoyconStick = new JoyconConfigKeyboardStick
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ },
+ },
+ };
+ }
+
+ public void Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath)
+ {
+ bool configurationFileUpdated = false;
+
+ if (configurationFileFormat.Version < 0 || configurationFileFormat.Version > ConfigurationFileFormat.CurrentVersion)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default.");
+
+ LoadDefault();
+ }
+
+ if (configurationFileFormat.Version < 2)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 2.");
+
+ configurationFileFormat.SystemRegion = Region.USA;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 3)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 3.");
+
+ configurationFileFormat.SystemTimeZone = "UTC";
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 4)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 4.");
+
+ configurationFileFormat.MaxAnisotropy = -1;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 5)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 5.");
+
+ configurationFileFormat.SystemTimeOffset = 0;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 8)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 8.");
+
+ configurationFileFormat.EnablePtc = true;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 9)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 9.");
+
+ configurationFileFormat.ColumnSort = new ColumnSort
+ {
+ SortColumnId = 0,
+ SortAscending = false,
+ };
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = Key.F1,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 10)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 10.");
+
+ configurationFileFormat.AudioBackend = AudioBackend.OpenAl;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 11)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 11.");
+
+ configurationFileFormat.ResScale = 1;
+ configurationFileFormat.ResScaleCustom = 1.0f;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 12)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 12.");
+
+ configurationFileFormat.LoggingGraphicsDebugLevel = GraphicsDebugLevel.None;
+
+ configurationFileUpdated = true;
+ }
+
+ // configurationFileFormat.Version == 13 -> LDN1
+
+ if (configurationFileFormat.Version < 14)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 14.");
+
+ configurationFileFormat.CheckUpdatesOnStart = true;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 16)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 16.");
+
+ configurationFileFormat.EnableShaderCache = true;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 17)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 17.");
+
+ configurationFileFormat.StartFullscreen = false;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 18)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 18.");
+
+ configurationFileFormat.AspectRatio = AspectRatio.Fixed16x9;
+
+ configurationFileUpdated = true;
+ }
+
+ // configurationFileFormat.Version == 19 -> LDN2
+
+ if (configurationFileFormat.Version < 20)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 20.");
+
+ configurationFileFormat.ShowConfirmExit = true;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 21)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 21.");
+
+ // Initialize network config.
+
+ configurationFileFormat.MultiplayerMode = MultiplayerMode.Disabled;
+ configurationFileFormat.MultiplayerLanInterfaceId = "0";
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 22)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22.");
+
+ configurationFileFormat.HideCursor = HideCursorMode.Never;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 24)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 24.");
+
+ configurationFileFormat.InputConfig = new List
+ {
+ new StandardKeyboardInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = "0",
+ PlayerIndex = PlayerIndex.Player1,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig
+ {
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound,
+ },
+ LeftJoyconStick = new JoyconConfigKeyboardStick
+ {
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
+ },
+ RightJoycon = new RightJoyconCommonConfig
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound,
+ },
+ RightJoyconStick = new JoyconConfigKeyboardStick
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ },
+ },
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 25)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 25.");
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 26)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 26.");
+
+ configurationFileFormat.MemoryManagerMode = MemoryManagerMode.HostMappedUnsafe;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 27)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 27.");
+
+ configurationFileFormat.EnableMouse = false;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 28)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 28.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = Key.F1,
+ Screenshot = Key.F8,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 29)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 29.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = Key.F1,
+ Screenshot = Key.F8,
+ ShowUI = Key.F4,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 30)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 30.");
+
+ foreach (InputConfig config in configurationFileFormat.InputConfig)
+ {
+ if (config is StandardControllerInputConfig controllerConfig)
+ {
+ controllerConfig.Rumble = new RumbleConfigController
+ {
+ EnableRumble = false,
+ StrongRumble = 1f,
+ WeakRumble = 1f,
+ };
+ }
+ }
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 31)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 31.");
+
+ configurationFileFormat.BackendThreading = BackendThreading.Auto;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 32)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 32.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUI = configurationFileFormat.Hotkeys.ShowUI,
+ Pause = Key.F5,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 33)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 33.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUI = configurationFileFormat.Hotkeys.ShowUI,
+ Pause = configurationFileFormat.Hotkeys.Pause,
+ ToggleMute = Key.F2,
+ };
+
+ configurationFileFormat.AudioVolume = 1;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 34)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 34.");
+
+ configurationFileFormat.EnableInternetAccess = false;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 35)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 35.");
+
+ foreach (InputConfig config in configurationFileFormat.InputConfig)
+ {
+ if (config is StandardControllerInputConfig controllerConfig)
+ {
+ controllerConfig.RangeLeft = 1.0f;
+ controllerConfig.RangeRight = 1.0f;
+ }
+ }
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 36)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 36.");
+
+ configurationFileFormat.LoggingEnableTrace = false;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 37)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 37.");
+
+ configurationFileFormat.ShowConsole = true;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 38)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 38.");
+
+ configurationFileFormat.BaseStyle = "Dark";
+ configurationFileFormat.GameListViewMode = 0;
+ configurationFileFormat.ShowNames = true;
+ configurationFileFormat.GridSize = 2;
+ configurationFileFormat.LanguageCode = "en_US";
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 39)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 39.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUI = configurationFileFormat.Hotkeys.ShowUI,
+ Pause = configurationFileFormat.Hotkeys.Pause,
+ ToggleMute = configurationFileFormat.Hotkeys.ToggleMute,
+ ResScaleUp = Key.Unbound,
+ ResScaleDown = Key.Unbound,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 40)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 40.");
+
+ configurationFileFormat.GraphicsBackend = GraphicsBackend.OpenGl;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 41)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 41.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUI = configurationFileFormat.Hotkeys.ShowUI,
+ Pause = configurationFileFormat.Hotkeys.Pause,
+ ToggleMute = configurationFileFormat.Hotkeys.ToggleMute,
+ ResScaleUp = configurationFileFormat.Hotkeys.ResScaleUp,
+ ResScaleDown = configurationFileFormat.Hotkeys.ResScaleDown,
+ VolumeUp = Key.Unbound,
+ VolumeDown = Key.Unbound,
+ };
+ }
+
+ if (configurationFileFormat.Version < 42)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 42.");
+
+ configurationFileFormat.EnableMacroHLE = true;
+ }
+
+ if (configurationFileFormat.Version < 43)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 43.");
+
+ configurationFileFormat.UseHypervisor = true;
+ }
+
+ if (configurationFileFormat.Version < 44)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 44.");
+
+ configurationFileFormat.AntiAliasing = AntiAliasing.None;
+ configurationFileFormat.ScalingFilter = ScalingFilter.Bilinear;
+ configurationFileFormat.ScalingFilterLevel = 80;
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 45)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 45.");
+
+ configurationFileFormat.ShownFileTypes = new ShownFileTypes
+ {
+ NSP = true,
+ PFS0 = true,
+ XCI = true,
+ NCA = true,
+ NRO = true,
+ NSO = true,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 46)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 46.");
+
+ configurationFileFormat.MultiplayerLanInterfaceId = "0";
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 47)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 47.");
+
+ configurationFileFormat.WindowStartup = new WindowStartup
+ {
+ WindowPositionX = 0,
+ WindowPositionY = 0,
+ WindowSizeHeight = 760,
+ WindowSizeWidth = 1280,
+ WindowMaximized = false,
+ };
+
+ configurationFileUpdated = true;
+ }
+
+ if (configurationFileFormat.Version < 48)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 48.");
+
+ configurationFileFormat.EnableColorSpacePassthrough = false;
+
+ configurationFileUpdated = true;
+ }
+
+ Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
+ Graphics.ResScale.Value = configurationFileFormat.ResScale;
+ Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
+ Graphics.MaxAnisotropy.Value = configurationFileFormat.MaxAnisotropy;
+ Graphics.AspectRatio.Value = configurationFileFormat.AspectRatio;
+ Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath;
+ Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
+ Graphics.GraphicsBackend.Value = configurationFileFormat.GraphicsBackend;
+ Graphics.PreferredGpu.Value = configurationFileFormat.PreferredGpu;
+ Graphics.AntiAliasing.Value = configurationFileFormat.AntiAliasing;
+ Graphics.ScalingFilter.Value = configurationFileFormat.ScalingFilter;
+ Graphics.ScalingFilterLevel.Value = configurationFileFormat.ScalingFilterLevel;
+ Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug;
+ Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub;
+ Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo;
+ Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn;
+ Logger.EnableError.Value = configurationFileFormat.LoggingEnableError;
+ Logger.EnableTrace.Value = configurationFileFormat.LoggingEnableTrace;
+ Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest;
+ Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog;
+ Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses;
+ Logger.GraphicsDebugLevel.Value = configurationFileFormat.LoggingGraphicsDebugLevel;
+ System.Language.Value = configurationFileFormat.SystemLanguage;
+ System.Region.Value = configurationFileFormat.SystemRegion;
+ System.TimeZone.Value = configurationFileFormat.SystemTimeZone;
+ System.SystemTimeOffset.Value = configurationFileFormat.SystemTimeOffset;
+ System.EnableDockedMode.Value = configurationFileFormat.DockedMode;
+ EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
+ CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
+ ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
+ HideCursor.Value = configurationFileFormat.HideCursor;
+ Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
+ Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;
+ Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression;
+ Graphics.EnableMacroHLE.Value = configurationFileFormat.EnableMacroHLE;
+ Graphics.EnableColorSpacePassthrough.Value = configurationFileFormat.EnableColorSpacePassthrough;
+ System.EnablePtc.Value = configurationFileFormat.EnablePtc;
+ System.EnableInternetAccess.Value = configurationFileFormat.EnableInternetAccess;
+ System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks;
+ System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode;
+ System.AudioBackend.Value = configurationFileFormat.AudioBackend;
+ System.AudioVolume.Value = configurationFileFormat.AudioVolume;
+ System.MemoryManagerMode.Value = configurationFileFormat.MemoryManagerMode;
+ System.ExpandRam.Value = configurationFileFormat.ExpandRam;
+ System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices;
+ System.UseHypervisor.Value = configurationFileFormat.UseHypervisor;
+ UI.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn;
+ UI.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn;
+ UI.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn;
+ UI.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn;
+ UI.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn;
+ UI.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn;
+ UI.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn;
+ UI.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn;
+ UI.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn;
+ UI.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn;
+ UI.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId;
+ UI.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending;
+ UI.GameDirs.Value = configurationFileFormat.GameDirs;
+ UI.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP;
+ UI.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0;
+ UI.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI;
+ UI.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA;
+ UI.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO;
+ UI.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO;
+ UI.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme;
+ UI.LanguageCode.Value = configurationFileFormat.LanguageCode;
+ UI.CustomThemePath.Value = configurationFileFormat.CustomThemePath;
+ UI.BaseStyle.Value = configurationFileFormat.BaseStyle;
+ UI.GameListViewMode.Value = configurationFileFormat.GameListViewMode;
+ UI.ShowNames.Value = configurationFileFormat.ShowNames;
+ UI.IsAscendingOrder.Value = configurationFileFormat.IsAscendingOrder;
+ UI.GridSize.Value = configurationFileFormat.GridSize;
+ UI.ApplicationSort.Value = configurationFileFormat.ApplicationSort;
+ UI.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
+ UI.ShowConsole.Value = configurationFileFormat.ShowConsole;
+ UI.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth;
+ UI.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight;
+ UI.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX;
+ UI.WindowStartup.WindowPositionY.Value = configurationFileFormat.WindowStartup.WindowPositionY;
+ UI.WindowStartup.WindowMaximized.Value = configurationFileFormat.WindowStartup.WindowMaximized;
+ Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
+ Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
+ Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;
+ Hid.InputConfig.Value = configurationFileFormat.InputConfig;
+
+ if (Hid.InputConfig.Value == null)
+ {
+ Hid.InputConfig.Value = new List();
+ }
+
+ Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
+ Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode;
+
+ if (configurationFileUpdated)
+ {
+ ToFileFormat().SaveConfig(configurationFilePath);
+
+ Ryujinx.Common.Logging.Logger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}");
+ }
+ }
+
+ private static GraphicsBackend DefaultGraphicsBackend()
+ {
+ // Any system running macOS or returning any amount of valid Vulkan devices should default to Vulkan.
+ // Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer.
+ if (OperatingSystem.IsMacOS() || VulkanRenderer.GetPhysicalDevices().Length > 0)
+ {
+ return GraphicsBackend.Vulkan;
+ }
+
+ return GraphicsBackend.OpenGl;
+ }
+
+ private static void LogValueChange(ReactiveEventArgs eventArgs, string valueName)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"{valueName} set to: {eventArgs.NewValue}");
+ }
+
+ public static void Initialize()
+ {
+ if (Instance != null)
+ {
+ throw new InvalidOperationException("Configuration is already initialized");
+ }
+
+ Instance = new ConfigurationState();
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/FileTypes.cs b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs
new file mode 100644
index 00000000..1974207b
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.UI.Common
+{
+ public enum FileTypes
+ {
+ NSP,
+ PFS0,
+ XCI,
+ NCA,
+ NRO,
+ NSO
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs
new file mode 100644
index 00000000..9cb28359
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs
@@ -0,0 +1,113 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Logging.Targets;
+using System;
+using System.IO;
+
+namespace Ryujinx.UI.Common.Configuration
+{
+ public static class LoggerModule
+ {
+ public static void Initialize()
+ {
+ ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug;
+ ConfigurationState.Instance.Logger.EnableStub.Event += ReloadEnableStub;
+ ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo;
+ ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning;
+ ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError;
+ ConfigurationState.Instance.Logger.EnableTrace.Event += ReloadEnableTrace;
+ ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest;
+ ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog;
+ ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses;
+ ConfigurationState.Instance.Logger.EnableFileLog.Event += ReloadFileLogger;
+ }
+
+ private static void ReloadEnableDebug(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Debug, e.NewValue);
+ }
+
+ private static void ReloadEnableStub(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Stub, e.NewValue);
+ }
+
+ private static void ReloadEnableInfo(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Info, e.NewValue);
+ }
+
+ private static void ReloadEnableWarning(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Warning, e.NewValue);
+ }
+
+ private static void ReloadEnableError(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Error, e.NewValue);
+ }
+
+ private static void ReloadEnableTrace(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Trace, e.NewValue);
+ }
+
+ private static void ReloadEnableGuest(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.Guest, e.NewValue);
+ }
+
+ private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs e)
+ {
+ Logger.SetEnable(LogLevel.AccessLog, e.NewValue);
+ }
+
+ private static void ReloadFilteredClasses(object sender, ReactiveEventArgs e)
+ {
+ bool noFilter = e.NewValue.Length == 0;
+
+ foreach (var logClass in Enum.GetValues())
+ {
+ Logger.SetEnable(logClass, noFilter);
+ }
+
+ foreach (var logClass in e.NewValue)
+ {
+ Logger.SetEnable(logClass, true);
+ }
+ }
+
+ private static void ReloadFileLogger(object sender, ReactiveEventArgs e)
+ {
+ if (e.NewValue)
+ {
+ string logDir = AppDataManager.LogsDirPath;
+ FileStream logFile = null;
+
+ if (!string.IsNullOrEmpty(logDir))
+ {
+ logFile = FileLogTarget.PrepareLogFile(logDir);
+ }
+
+ if (logFile == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable.");
+ Logger.RemoveTarget("file");
+
+ return;
+ }
+
+ Logger.AddTarget(new AsyncLogTargetWrapper(
+ new FileLogTarget("file", logFile),
+ 1000,
+ AsyncLogTargetOverflowAction.Block
+ ));
+ }
+ else
+ {
+ Logger.RemoveTarget("file");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/System/Language.cs b/src/Ryujinx.UI.Common/Configuration/System/Language.cs
new file mode 100644
index 00000000..d1d395b0
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/System/Language.cs
@@ -0,0 +1,28 @@
+using Ryujinx.Common.Utilities;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.Common.Configuration.System
+{
+ [JsonConverter(typeof(TypedStringEnumConverter))]
+ public enum Language
+ {
+ Japanese,
+ AmericanEnglish,
+ French,
+ German,
+ Italian,
+ Spanish,
+ Chinese,
+ Korean,
+ Dutch,
+ Portuguese,
+ Russian,
+ Taiwanese,
+ BritishEnglish,
+ CanadianFrench,
+ LatinAmericanSpanish,
+ SimplifiedChinese,
+ TraditionalChinese,
+ BrazilianPortuguese,
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/System/Region.cs b/src/Ryujinx.UI.Common/Configuration/System/Region.cs
new file mode 100644
index 00000000..6087c70e
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/System/Region.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Common.Utilities;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.Common.Configuration.System
+{
+ [JsonConverter(typeof(TypedStringEnumConverter))]
+ public enum Region
+ {
+ Japan,
+ USA,
+ Europe,
+ Australia,
+ China,
+ Korea,
+ Taiwan,
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs
new file mode 100644
index 00000000..44e98c40
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.UI.Common.Configuration.UI
+{
+ public struct ColumnSort
+ {
+ public int SortColumnId { get; set; }
+ public bool SortAscending { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs
new file mode 100644
index 00000000..c778ef1f
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.UI.Common.Configuration.UI
+{
+ public struct GuiColumns
+ {
+ public bool FavColumn { get; set; }
+ public bool IconColumn { get; set; }
+ public bool AppColumn { get; set; }
+ public bool DevColumn { get; set; }
+ public bool VersionColumn { get; set; }
+ public bool TimePlayedColumn { get; set; }
+ public bool LastPlayedColumn { get; set; }
+ public bool FileExtColumn { get; set; }
+ public bool FileSizeColumn { get; set; }
+ public bool PathColumn { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs
new file mode 100644
index 00000000..6c72a693
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.UI.Common.Configuration.UI
+{
+ public struct ShownFileTypes
+ {
+ public bool NSP { get; set; }
+ public bool PFS0 { get; set; }
+ public bool XCI { get; set; }
+ public bool NCA { get; set; }
+ public bool NRO { get; set; }
+ public bool NSO { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs
new file mode 100644
index 00000000..0df45913
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.UI.Common.Configuration.UI
+{
+ public struct WindowStartup
+ {
+ public int WindowSizeWidth { get; set; }
+ public int WindowSizeHeight { get; set; }
+ public int WindowPositionX { get; set; }
+ public int WindowPositionY { get; set; }
+ public bool WindowMaximized { get; set; }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
new file mode 100644
index 00000000..0b9439ea
--- /dev/null
+++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
@@ -0,0 +1,98 @@
+using DiscordRPC;
+using Ryujinx.Common;
+using Ryujinx.UI.Common.Configuration;
+
+namespace Ryujinx.UI.Common
+{
+ public static class DiscordIntegrationModule
+ {
+ private const string Description = "A simple, experimental Nintendo Switch emulator.";
+ private const string CliendId = "568815339807309834";
+
+ private static DiscordRpcClient _discordClient;
+ private static RichPresence _discordPresenceMain;
+
+ public static void Initialize()
+ {
+ _discordPresenceMain = new RichPresence
+ {
+ Assets = new Assets
+ {
+ LargeImageKey = "ryujinx",
+ LargeImageText = Description,
+ },
+ Details = "Main Menu",
+ State = "Idling",
+ Timestamps = Timestamps.Now,
+ Buttons = new[]
+ {
+ new Button
+ {
+ Label = "Website",
+ Url = "https://ryujinx.org/",
+ },
+ },
+ };
+
+ ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
+ }
+
+ private static void Update(object sender, ReactiveEventArgs evnt)
+ {
+ if (evnt.OldValue != evnt.NewValue)
+ {
+ // If the integration was active, disable it and unload everything
+ if (evnt.OldValue)
+ {
+ _discordClient?.Dispose();
+
+ _discordClient = null;
+ }
+
+ // If we need to activate it and the client isn't active, initialize it
+ if (evnt.NewValue && _discordClient == null)
+ {
+ _discordClient = new DiscordRpcClient(CliendId);
+
+ _discordClient.Initialize();
+ _discordClient.SetPresence(_discordPresenceMain);
+ }
+ }
+ }
+
+ public static void SwitchToPlayingState(string titleId, string titleName)
+ {
+ _discordClient?.SetPresence(new RichPresence
+ {
+ Assets = new Assets
+ {
+ LargeImageKey = "game",
+ LargeImageText = titleName,
+ SmallImageKey = "ryujinx",
+ SmallImageText = Description,
+ },
+ Details = $"Playing {titleName}",
+ State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(),
+ Timestamps = Timestamps.Now,
+ Buttons = new[]
+ {
+ new Button
+ {
+ Label = "Website",
+ Url = "https://ryujinx.org/",
+ },
+ },
+ });
+ }
+
+ public static void SwitchToMainMenu()
+ {
+ _discordClient?.SetPresence(_discordPresenceMain);
+ }
+
+ public static void Exit()
+ {
+ _discordClient?.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs
new file mode 100644
index 00000000..7e71ba7a
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using static Ryujinx.UI.Common.Configuration.ConfigurationState.UISection;
+
+namespace Ryujinx.UI.Common
+{
+ public static class FileTypesExtensions
+ {
+ ///
+ /// Gets the current value for the correlating FileType name.
+ ///
+ /// The name of the parameter to get the value of.
+ /// The config instance to get the value from.
+ /// The current value of the setting. Value is if the file type is the be shown on the games list, otherwise.
+ public static bool GetConfigValue(this FileTypes type, ShownFileTypeSettings config) => type switch
+ {
+ FileTypes.NSP => config.NSP.Value,
+ FileTypes.PFS0 => config.PFS0.Value,
+ FileTypes.XCI => config.XCI.Value,
+ FileTypes.NCA => config.NCA.Value,
+ FileTypes.NRO => config.NRO.Value,
+ FileTypes.NSO => config.NSO.Value,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
+ };
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs
new file mode 100644
index 00000000..c3c5bd37
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Common.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static class CommandLineState
+ {
+ public static string[] Arguments { get; private set; }
+
+ public static bool? OverrideDockedMode { get; private set; }
+ public static string OverrideGraphicsBackend { get; private set; }
+ public static string OverrideHideCursor { get; private set; }
+ public static string BaseDirPathArg { get; private set; }
+ public static string Profile { get; private set; }
+ public static string LaunchPathArg { get; private set; }
+ public static bool StartFullscreenArg { get; private set; }
+
+ public static void ParseArguments(string[] args)
+ {
+ List arguments = new();
+
+ // Parse Arguments.
+ for (int i = 0; i < args.Length; ++i)
+ {
+ string arg = args[i];
+
+ switch (arg)
+ {
+ case "-r":
+ case "--root-data-dir":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ BaseDirPathArg = args[++i];
+
+ arguments.Add(arg);
+ arguments.Add(args[i]);
+ break;
+ case "-p":
+ case "--profile":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ Profile = args[++i];
+
+ arguments.Add(arg);
+ arguments.Add(args[i]);
+ break;
+ case "-f":
+ case "--fullscreen":
+ StartFullscreenArg = true;
+
+ arguments.Add(arg);
+ break;
+ case "-g":
+ case "--graphics-backend":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ OverrideGraphicsBackend = args[++i];
+ break;
+ case "--docked-mode":
+ OverrideDockedMode = true;
+ break;
+ case "--handheld-mode":
+ OverrideDockedMode = false;
+ break;
+ case "--hide-cursor":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ OverrideHideCursor = args[++i];
+ break;
+ default:
+ LaunchPathArg = arg;
+ break;
+ }
+ }
+
+ Arguments = arguments.ToArray();
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs
new file mode 100644
index 00000000..208ff5c9
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs
@@ -0,0 +1,50 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static partial class ConsoleHelper
+ {
+ public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
+
+ public static void SetConsoleWindowState(bool show)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ SetConsoleWindowStateWindows(show);
+ }
+ else if (show == false)
+ {
+ Logger.Warning?.Print(LogClass.Application, "OS doesn't support hiding console window");
+ }
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static void SetConsoleWindowStateWindows(bool show)
+ {
+ const int SW_HIDE = 0;
+ const int SW_SHOW = 5;
+
+ IntPtr hWnd = GetConsoleWindow();
+
+ if (hWnd == IntPtr.Zero)
+ {
+ Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
+ return;
+ }
+
+ ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
+ }
+
+ [SupportedOSPlatform("windows")]
+ [LibraryImport("kernel32")]
+ private static partial IntPtr GetConsoleWindow();
+
+ [SupportedOSPlatform("windows")]
+ [LibraryImport("user32")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs
new file mode 100644
index 00000000..7ed02031
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs
@@ -0,0 +1,202 @@
+using Microsoft.Win32;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static partial class FileAssociationHelper
+ {
+ private static readonly string[] _fileExtensions = { ".nca", ".nro", ".nso", ".nsp", ".xci" };
+
+ [SupportedOSPlatform("linux")]
+ private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
+
+ private const int SHCNE_ASSOCCHANGED = 0x8000000;
+ private const int SHCNF_FLUSH = 0x1000;
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
+
+ public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild;
+
+ [SupportedOSPlatform("linux")]
+ private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
+
+ [SupportedOSPlatform("linux")]
+ private static bool InstallLinuxMimeTypes(bool uninstall = false)
+ {
+ string installKeyword = uninstall ? "uninstall" : "install";
+
+ if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
+ {
+ string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml");
+ string additionalArgs = !uninstall ? "--novendor" : "";
+
+ using Process mimeProcess = new();
+
+ mimeProcess.StartInfo.FileName = "xdg-mime";
+ mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}";
+
+ mimeProcess.Start();
+ mimeProcess.WaitForExit();
+
+ if (mimeProcess.ExitCode != 0)
+ {
+ Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
+
+ return false;
+ }
+
+ using Process updateMimeProcess = new();
+
+ updateMimeProcess.StartInfo.FileName = "update-mime-database";
+ updateMimeProcess.StartInfo.Arguments = _mimeDbPath;
+
+ updateMimeProcess.Start();
+ updateMimeProcess.WaitForExit();
+
+ if (updateMimeProcess.ExitCode != 0)
+ {
+ Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
+ }
+ }
+
+ return true;
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static bool AreMimeTypesRegisteredWindows()
+ {
+ static bool CheckRegistering(string ext)
+ {
+ RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");
+
+ if (key is null)
+ {
+ return false;
+ }
+
+ var openCmd = key.OpenSubKey(@"shell\open\command");
+
+ string keyValue = (string)openCmd.GetValue("");
+
+ return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
+ }
+
+ bool registered = false;
+
+ foreach (string ext in _fileExtensions)
+ {
+ registered |= CheckRegistering(ext);
+ }
+
+ return registered;
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static bool InstallWindowsMimeTypes(bool uninstall = false)
+ {
+ static bool RegisterExtension(string ext, bool uninstall = false)
+ {
+ string keyString = @$"Software\Classes\{ext}";
+
+ if (uninstall)
+ {
+ // If the types don't already exist, there's nothing to do and we can call this operation successful.
+ if (!AreMimeTypesRegisteredWindows())
+ {
+ return true;
+ }
+ Logger.Debug?.Print(LogClass.Application, $"Removing type association {ext}");
+ Registry.CurrentUser.DeleteSubKeyTree(keyString);
+ Logger.Debug?.Print(LogClass.Application, $"Removed type association {ext}");
+ }
+ else
+ {
+ using var key = Registry.CurrentUser.CreateSubKey(keyString);
+
+ if (key is null)
+ {
+ return false;
+ }
+
+ Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}");
+ using var openCmd = key.CreateSubKey(@"shell\open\command");
+ openCmd.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
+ Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}");
+
+ }
+
+ return true;
+ }
+
+ bool registered = false;
+
+ foreach (string ext in _fileExtensions)
+ {
+ registered |= RegisterExtension(ext, uninstall);
+ }
+
+ // Notify Explorer the file association has been changed.
+ SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
+
+ return registered;
+ }
+
+ public static bool AreMimeTypesRegistered()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return AreMimeTypesRegisteredLinux();
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return AreMimeTypesRegisteredWindows();
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+
+ public static bool Install()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return InstallLinuxMimeTypes();
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return InstallWindowsMimeTypes();
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+
+ public static bool Uninstall()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return InstallLinuxMimeTypes(true);
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return InstallWindowsMimeTypes(true);
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs
new file mode 100644
index 00000000..b5779379
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ [SupportedOSPlatform("linux")]
+ public static class LinuxHelper
+ {
+ // NOTE: This value was determined by manual tests and might need to be increased again.
+ public const int RecommendedVmMaxMapCount = 524288;
+ public const string VmMaxMapCountPath = "/proc/sys/vm/max_map_count";
+ public const string SysCtlConfigPath = "/etc/sysctl.d/99-Ryujinx.conf";
+ public static int VmMaxMapCount => int.Parse(File.ReadAllText(VmMaxMapCountPath));
+ public static string PkExecPath { get; } = GetBinaryPath("pkexec");
+
+ private static string GetBinaryPath(string binary)
+ {
+ string pathVar = Environment.GetEnvironmentVariable("PATH");
+
+ if (pathVar is null || string.IsNullOrEmpty(binary))
+ {
+ return null;
+ }
+
+ foreach (var searchPath in pathVar.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
+ {
+ string binaryPath = Path.Combine(searchPath, binary);
+
+ if (File.Exists(binaryPath))
+ {
+ return binaryPath;
+ }
+ }
+
+ return null;
+ }
+
+ public static int RunPkExec(string command)
+ {
+ if (PkExecPath == null)
+ {
+ return 1;
+ }
+
+ using Process process = new()
+ {
+ StartInfo =
+ {
+ FileName = PkExecPath,
+ ArgumentList = { "sh", "-c", command },
+ },
+ };
+
+ process.Start();
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs
new file mode 100644
index 00000000..6aba377a
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ [SupportedOSPlatform("macos")]
+ public static partial class ObjectiveC
+ {
+ private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
+
+ [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
+ private static partial IntPtr sel_getUid(string name);
+
+ [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
+ private static partial IntPtr objc_getClass(string name);
+
+ [LibraryImport(ObjCRuntime)]
+ private static partial void objc_msgSend(IntPtr receiver, Selector selector);
+
+ [LibraryImport(ObjCRuntime)]
+ private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
+
+ [LibraryImport(ObjCRuntime)]
+ private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
+
+ [LibraryImport(ObjCRuntime)]
+ private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
+
+ [LibraryImport(ObjCRuntime)]
+ private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
+
+ public readonly struct Object
+ {
+ public readonly IntPtr ObjPtr;
+
+ private Object(IntPtr pointer)
+ {
+ ObjPtr = pointer;
+ }
+
+ public Object(string name)
+ {
+ ObjPtr = objc_getClass(name);
+ }
+
+ public void SendMessage(Selector selector)
+ {
+ objc_msgSend(ObjPtr, selector);
+ }
+
+ public void SendMessage(Selector selector, byte value)
+ {
+ objc_msgSend(ObjPtr, selector, value);
+ }
+
+ public void SendMessage(Selector selector, Object obj)
+ {
+ objc_msgSend(ObjPtr, selector, obj.ObjPtr);
+ }
+
+ public void SendMessage(Selector selector, NSRect point)
+ {
+ objc_msgSend(ObjPtr, selector, point);
+ }
+
+ public void SendMessage(Selector selector, double value)
+ {
+ objc_msgSend(ObjPtr, selector, value);
+ }
+
+ public Object GetFromMessage(Selector selector)
+ {
+ return new Object(IntPtr_objc_msgSend(ObjPtr, selector));
+ }
+
+ public Object GetFromMessage(Selector selector, Object obj)
+ {
+ return new Object(IntPtr_objc_msgSend(ObjPtr, selector, obj.ObjPtr));
+ }
+
+ public Object GetFromMessage(Selector selector, NSString nsString)
+ {
+ return new Object(IntPtr_objc_msgSend(ObjPtr, selector, nsString.StrPtr));
+ }
+
+ public Object GetFromMessage(Selector selector, string param)
+ {
+ return new Object(IntPtr_objc_msgSend(ObjPtr, selector, param));
+ }
+
+ public bool GetBoolFromMessage(Selector selector, Object obj)
+ {
+ return bool_objc_msgSend(ObjPtr, selector, obj.ObjPtr);
+ }
+ }
+
+ public readonly struct Selector
+ {
+ public readonly IntPtr SelPtr;
+
+ private Selector(string name)
+ {
+ SelPtr = sel_getUid(name);
+ }
+
+ public static implicit operator Selector(string value) => new(value);
+ }
+
+ public readonly struct NSString
+ {
+ public readonly IntPtr StrPtr;
+
+ public NSString(string aString)
+ {
+ IntPtr nsString = objc_getClass("NSString");
+ StrPtr = IntPtr_objc_msgSend(nsString, "stringWithUTF8String:", aString);
+ }
+
+ public static implicit operator IntPtr(NSString nsString) => nsString.StrPtr;
+ }
+
+ public readonly struct NSPoint
+ {
+ public readonly double X;
+ public readonly double Y;
+
+ public NSPoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+ }
+
+ public readonly struct NSRect
+ {
+ public readonly NSPoint Pos;
+ public readonly NSPoint Size;
+
+ public NSRect(double x, double y, double width, double height)
+ {
+ Pos = new NSPoint(x, y);
+ Size = new NSPoint(width, height);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/OpenHelper.cs b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs
new file mode 100644
index 00000000..af6170af
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs
@@ -0,0 +1,112 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static partial class OpenHelper
+ {
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ private static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags);
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ private static partial void ILFree(IntPtr pidlList);
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ private static partial IntPtr ILCreateFromPathW([MarshalAs(UnmanagedType.LPWStr)] string pszPath);
+
+ public static void OpenFolder(string path)
+ {
+ if (Directory.Exists(path))
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = path,
+ UseShellExecute = true,
+ Verb = "open",
+ });
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"Directory \"{path}\" doesn't exist!");
+ }
+ }
+
+ public static void LocateFile(string path)
+ {
+ if (File.Exists(path))
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ IntPtr pidlList = ILCreateFromPathW(path);
+ if (pidlList != IntPtr.Zero)
+ {
+ try
+ {
+ Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
+ }
+ finally
+ {
+ ILFree(pidlList);
+ }
+ }
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ ObjectiveC.NSString nsStringPath = new(path);
+ ObjectiveC.Object nsUrl = new("NSURL");
+ var urlPtr = nsUrl.GetFromMessage("fileURLWithPath:", nsStringPath);
+
+ ObjectiveC.Object nsArray = new("NSArray");
+ ObjectiveC.Object urlArray = nsArray.GetFromMessage("arrayWithObject:", urlPtr);
+
+ ObjectiveC.Object nsWorkspace = new("NSWorkspace");
+ ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace");
+
+ sharedWorkspace.SendMessage("activateFileViewerSelectingURLs:", urlArray);
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ Process.Start("dbus-send", $"--session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"file://{path}\" string:\"\"");
+ }
+ else
+ {
+ OpenFolder(Path.GetDirectoryName(path));
+ }
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"File \"{path}\" doesn't exist!");
+ }
+ }
+
+ public static void OpenUrl(string url)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}"));
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ Process.Start("xdg-open", url);
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ ObjectiveC.NSString nsStringPath = new(url);
+ ObjectiveC.Object nsUrl = new("NSURL");
+ var urlPtr = nsUrl.GetFromMessage("URLWithString:", nsStringPath);
+
+ ObjectiveC.Object nsWorkspace = new("NSWorkspace");
+ ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace");
+
+ sharedWorkspace.GetBoolFromMessage("openURL:", urlPtr);
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"Cannot open url \"{url}\" on this platform!");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/SetupValidator.cs b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs
new file mode 100644
index 00000000..a954be26
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs
@@ -0,0 +1,114 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using System;
+using System.IO;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ ///
+ /// Ensure installation validity
+ ///
+ public static class SetupValidator
+ {
+ public static bool IsFirmwareValid(ContentManager contentManager, out UserError error)
+ {
+ bool hasFirmware = contentManager.GetCurrentFirmwareVersion() != null;
+
+ if (hasFirmware)
+ {
+ error = UserError.Success;
+
+ return true;
+ }
+
+ error = UserError.NoFirmware;
+
+ return false;
+ }
+
+ public static bool CanFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out SystemVersion firmwareVersion)
+ {
+ try
+ {
+ firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+ }
+ catch (Exception)
+ {
+ firmwareVersion = null;
+ }
+
+ return error == UserError.NoFirmware && Path.GetExtension(baseApplicationPath).ToLowerInvariant() == ".xci" && firmwareVersion != null;
+ }
+
+ public static bool TryFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out UserError outError)
+ {
+ if (error == UserError.NoFirmware)
+ {
+ string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+ // If the target app to start is a XCI, try to install firmware from it
+ if (baseApplicationExtension == ".xci")
+ {
+ SystemVersion firmwareVersion;
+
+ try
+ {
+ firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+ }
+ catch (Exception)
+ {
+ firmwareVersion = null;
+ }
+
+ // The XCI is a valid firmware package, try to install the firmware from it!
+ if (firmwareVersion != null)
+ {
+ try
+ {
+ Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
+
+ contentManager.InstallFirmware(baseApplicationPath);
+
+ Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed.");
+
+ outError = UserError.Success;
+
+ return true;
+ }
+ catch (Exception) { }
+ }
+
+ outError = error;
+
+ return false;
+ }
+ }
+
+ outError = error;
+
+ return false;
+ }
+
+ public static bool CanStartApplication(ContentManager contentManager, string baseApplicationPath, out UserError error)
+ {
+ if (Directory.Exists(baseApplicationPath) || File.Exists(baseApplicationPath))
+ {
+ string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+ // NOTE: We don't force homebrew developers to install a system firmware.
+ if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso")
+ {
+ error = UserError.Success;
+
+ return true;
+ }
+
+ return IsFirmwareValid(contentManager, out error);
+ }
+
+ error = UserError.ApplicationNotFound;
+
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
new file mode 100644
index 00000000..c2085b28
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
@@ -0,0 +1,162 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using ShellLink;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static class ShortcutHelper
+ {
+ [SupportedOSPlatform("windows")]
+ private static void CreateShortcutWindows(string applicationFilePath, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
+ {
+ string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
+ iconPath += ".ico";
+
+ MemoryStream iconDataStream = new(iconData);
+ var image = Image.Load(iconDataStream);
+ image.Mutate(x => x.Resize(128, 128));
+ SaveBitmapAsIcon(image, iconPath);
+
+ var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath), iconPath, 0);
+ shortcut.StringData.NameString = cleanedAppName;
+ shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
+ }
+
+ [SupportedOSPlatform("linux")]
+ private static void CreateShortcutLinux(string applicationFilePath, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
+ {
+ string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
+ var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
+ iconPath += ".png";
+
+ var image = Image.Load(iconData);
+ image.SaveAsPng(iconPath);
+
+ using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
+ outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath)}");
+ }
+
+ [SupportedOSPlatform("macos")]
+ private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
+ {
+ string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
+ var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist");
+ var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-launch-script.sh");
+ // Macos .App folder
+ string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents");
+ string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
+
+ if (!Directory.Exists(scriptFolderPath))
+ {
+ Directory.CreateDirectory(scriptFolderPath);
+ }
+
+ // Runner script
+ const string ScriptName = "runner.sh";
+ string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
+ using StreamWriter scriptFile = new(scriptPath);
+
+ scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath));
+
+ // Set execute permission
+ FileInfo fileInfo = new(scriptPath);
+ fileInfo.UnixFileMode |= UnixFileMode.UserExecute;
+
+ // img
+ string resourceFolderPath = Path.Combine(contentFolderPath, "Resources");
+ if (!Directory.Exists(resourceFolderPath))
+ {
+ Directory.CreateDirectory(resourceFolderPath);
+ }
+
+ const string IconName = "icon.png";
+ var image = Image.Load(iconData);
+ image.SaveAsPng(Path.Combine(resourceFolderPath, IconName));
+
+ // plist file
+ using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
+ outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
+ }
+
+ public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
+ {
+ string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
+ string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
+
+ if (OperatingSystem.IsWindows())
+ {
+ string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
+
+ CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
+
+ return;
+ }
+
+ if (OperatingSystem.IsLinux())
+ {
+ string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
+
+ Directory.CreateDirectory(iconPath);
+ CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
+
+ return;
+ }
+
+ if (OperatingSystem.IsMacOS())
+ {
+ CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
+
+ return;
+ }
+
+ throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
+ }
+
+ private static string GetArgsString(string appFilePath)
+ {
+ // args are first defined as a list, for easier adjustments in the future
+ var argsList = new List();
+
+ if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
+ {
+ argsList.Add("--root-data-dir");
+ argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
+ }
+
+ argsList.Add($"\"{appFilePath}\"");
+
+ return String.Join(" ", argsList);
+ }
+
+ ///
+ /// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
+ ///
+ /// The source bitmap image that will be saved as an .ico file
+ /// The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).
+ [SupportedOSPlatform("windows")]
+ private static void SaveBitmapAsIcon(Image source, string filePath)
+ {
+ // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
+ byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
+ using FileStream fs = new(filePath, FileMode.Create);
+
+ fs.Write(header);
+ // Writing actual data
+ source.Save(fs, PngFormat.Instance);
+ // Getting data length (file length minus header)
+ long dataLength = fs.Length - header.Length;
+ // Write it in the correct place
+ fs.Seek(14, SeekOrigin.Begin);
+ fs.WriteByte((byte)dataLength);
+ fs.WriteByte((byte)(dataLength >> 8));
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/TitleHelper.cs b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs
new file mode 100644
index 00000000..8b47ac38
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs
@@ -0,0 +1,30 @@
+using Ryujinx.HLE.Loaders.Processes;
+using System;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static class TitleHelper
+ {
+ public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
+ {
+ if (activeProcess == null)
+ {
+ return String.Empty;
+ }
+
+ string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
+ string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
+ string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
+ string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
+
+ string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
+
+ if (!string.IsNullOrEmpty(pauseString))
+ {
+ appTitle += $" ({pauseString})";
+ }
+
+ return appTitle;
+ }
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs
new file mode 100644
index 00000000..8ea3e721
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs
@@ -0,0 +1,219 @@
+using System;
+using System.Globalization;
+using System.Linq;
+
+namespace Ryujinx.UI.Common.Helper
+{
+ public static class ValueFormatUtils
+ {
+ private static readonly string[] _fileSizeUnitStrings =
+ {
+ "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", // Base 10 units, used for formatting and parsing
+ "KB", "MB", "GB", "TB", "PB", "EB", // Base 2 units, used for parsing legacy values
+ };
+
+ ///
+ /// Used by .
+ ///
+ public enum FileSizeUnits
+ {
+ Auto = -1,
+ Bytes = 0,
+ Kibibytes = 1,
+ Mebibytes = 2,
+ Gibibytes = 3,
+ Tebibytes = 4,
+ Pebibytes = 5,
+ Exbibytes = 6,
+ Kilobytes = 7,
+ Megabytes = 8,
+ Gigabytes = 9,
+ Terabytes = 10,
+ Petabytes = 11,
+ Exabytes = 12,
+ }
+
+ private const double SizeBase10 = 1000;
+ private const double SizeBase2 = 1024;
+ private const int UnitEBIndex = 6;
+
+ #region Value formatters
+
+ ///
+ /// Creates a human-readable string from a .
+ ///
+ /// The to be formatted.
+ /// A formatted string that can be displayed in the UI.
+ public static string FormatTimeSpan(TimeSpan? timeSpan)
+ {
+ if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
+ {
+ // Game was never played
+ return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
+ }
+
+ if (timeSpan.Value.TotalDays < 1)
+ {
+ // Game was played for less than a day
+ return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture);
+ }
+
+ // Game was played for more than a day
+ TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days));
+ string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture);
+
+ return $"{timeSpan.Value.Days}d, {onlyTimeString}";
+ }
+
+ ///
+ /// Creates a human-readable string from a .
+ ///
+ /// The to be formatted. This is expected to be UTC-based.
+ /// The that's used in formatting. Defaults to .
+ /// A formatted string that can be displayed in the UI.
+ public static string FormatDateTime(DateTime? utcDateTime, CultureInfo culture = null)
+ {
+ culture ??= CultureInfo.CurrentCulture;
+
+ if (!utcDateTime.HasValue)
+ {
+ // In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
+ return "Never";
+ }
+
+ return utcDateTime.Value.ToLocalTime().ToString(culture);
+ }
+
+ ///
+ /// Creates a human-readable file size string.
+ ///
+ /// The file size in bytes.
+ /// Formats the passed size value as this unit, bypassing the automatic unit choice.
+ /// A human-readable file size string.
+ public static string FormatFileSize(long size, FileSizeUnits forceUnit = FileSizeUnits.Auto)
+ {
+ if (size <= 0)
+ {
+ return $"0 {_fileSizeUnitStrings[0]}";
+ }
+
+ int unitIndex = (int)forceUnit;
+ if (forceUnit == FileSizeUnits.Auto)
+ {
+ unitIndex = Convert.ToInt32(Math.Floor(Math.Log(size, SizeBase10)));
+
+ // Apply an upper bound so that exabytes are the biggest unit used when formatting.
+ if (unitIndex > UnitEBIndex)
+ {
+ unitIndex = UnitEBIndex;
+ }
+ }
+
+ double sizeRounded;
+
+ if (unitIndex > UnitEBIndex)
+ {
+ sizeRounded = Math.Round(size / Math.Pow(SizeBase10, unitIndex - UnitEBIndex), 1);
+ }
+ else
+ {
+ sizeRounded = Math.Round(size / Math.Pow(SizeBase2, unitIndex), 1);
+ }
+
+ string sizeFormatted = sizeRounded.ToString(CultureInfo.InvariantCulture);
+
+ return $"{sizeFormatted} {_fileSizeUnitStrings[unitIndex]}";
+ }
+
+ #endregion
+
+ #region Value parsers
+
+ ///
+ /// Parses a string generated by and returns the original .
+ ///
+ /// A string representing a .
+ /// A object. If the input string couldn't been parsed, is returned.
+ public static TimeSpan ParseTimeSpan(string timeSpanString)
+ {
+ TimeSpan returnTimeSpan = TimeSpan.Zero;
+
+ // An input string can either look like "01:23:45" or "1d, 01:23:45" if the timespan represents a duration of more than a day.
+ // Here, we split the input string to check if it's the former or the latter.
+ var valueSplit = timeSpanString.Split(", ");
+ if (valueSplit.Length > 1)
+ {
+ var dayPart = valueSplit[0].Split("d")[0];
+ if (int.TryParse(dayPart, out int days))
+ {
+ returnTimeSpan = returnTimeSpan.Add(TimeSpan.FromDays(days));
+ }
+ }
+
+ if (TimeSpan.TryParse(valueSplit.Last(), out TimeSpan parsedTimeSpan))
+ {
+ returnTimeSpan = returnTimeSpan.Add(parsedTimeSpan);
+ }
+
+ return returnTimeSpan;
+ }
+
+ ///
+ /// Parses a string generated by and returns the original .
+ ///
+ /// The string representing a .
+ /// A object. If the input string couldn't be parsed, is returned.
+ public static DateTime ParseDateTime(string dateTimeString)
+ {
+ if (!DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, out DateTime parsedDateTime))
+ {
+ // Games that were never played are supposed to appear before the oldest played games in the list,
+ // so returning DateTime.UnixEpoch here makes sense.
+ return DateTime.UnixEpoch;
+ }
+
+ return parsedDateTime;
+ }
+
+ ///
+ /// Parses a string generated by and returns a representing a number of bytes.
+ ///
+ /// A string representing a file size formatted with .
+ /// A representing a number of bytes.
+ public static long ParseFileSize(string sizeString)
+ {
+ // Enumerating over the units backwards because otherwise, sizeString.EndsWith("B") would exit the loop in the first iteration.
+ for (int i = _fileSizeUnitStrings.Length - 1; i >= 0; i--)
+ {
+ string unit = _fileSizeUnitStrings[i];
+ if (!sizeString.EndsWith(unit))
+ {
+ continue;
+ }
+
+ string numberString = sizeString.Split(" ")[0];
+ if (!double.TryParse(numberString, CultureInfo.InvariantCulture, out double number))
+ {
+ break;
+ }
+
+ double sizeBase = SizeBase2;
+
+ // If the unit index is one that points to a base 10 unit in the FileSizeUnitStrings array, subtract 6 to arrive at a usable power value.
+ if (i > UnitEBIndex)
+ {
+ i -= UnitEBIndex;
+ sizeBase = SizeBase10;
+ }
+
+ number *= Math.Pow(sizeBase, i);
+
+ return Convert.ToInt64(number);
+ }
+
+ return 0;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs
new file mode 100644
index 00000000..7989f0f1
--- /dev/null
+++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.UI.Common.Models.Amiibo
+{
+ public struct AmiiboApi : IEquatable
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ [JsonPropertyName("head")]
+ public string Head { get; set; }
+ [JsonPropertyName("tail")]
+ public string Tail { get; set; }
+ [JsonPropertyName("image")]
+ public string Image { get; set; }
+ [JsonPropertyName("amiiboSeries")]
+ public string AmiiboSeries { get; set; }
+ [JsonPropertyName("character")]
+ public string Character { get; set; }
+ [JsonPropertyName("gameSeries")]
+ public string GameSeries { get; set; }
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("release")]
+ public Dictionary Release { get; set; }
+
+ [JsonPropertyName("gamesSwitch")]
+ public List