From ec6cb0abb4b7669895b6e96fd7581c93b5abd691 Mon Sep 17 00:00:00 2001 From: Mary Guillemard <mary@mary.zone> Date: Sat, 2 Mar 2024 12:51:05 +0100 Subject: infra: Make Avalonia the default UI (#6375) * misc: Move Ryujinx project to Ryujinx.Gtk3 This breaks release CI for now but that's fine. Signed-off-by: Mary Guillemard <mary@mary.zone> * misc: Move Ryujinx.Ava project to Ryujinx This breaks CI for now, but it's fine. Signed-off-by: Mary Guillemard <mary@mary.zone> * infra: Make Avalonia the default UI Should fix CI after the previous changes. GTK3 isn't build by the release job anymore, only by PR CI. This also ensure that the test-ava update package is still generated to allow update from the old testing channel. Signed-off-by: Mary Guillemard <mary@mary.zone> * Fix missing copy in create_app_bundle.sh Signed-off-by: Mary Guillemard <mary@mary.zone> * Fix syntax error Signed-off-by: Mary Guillemard <mary@mary.zone> --------- Signed-off-by: Mary Guillemard <mary@mary.zone> --- src/Ryujinx.Ava/App.axaml | 17 - src/Ryujinx.Ava/App.axaml.cs | 115 - src/Ryujinx.Ava/AppHost.cs | 1195 -------- src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf | Bin 408752 -> 0 bytes .../Assets/Icons/Controller_JoyConLeft.svg | 155 - .../Assets/Icons/Controller_JoyConPair.svg | 341 --- .../Assets/Icons/Controller_JoyConRight.svg | 185 -- src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg | 84 - src/Ryujinx.Ava/Assets/Locales/de_DE.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/el_GR.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/en_US.json | 668 ---- src/Ryujinx.Ava/Assets/Locales/es_ES.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/fr_FR.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/he_IL.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/it_IT.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/ja_JP.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/ko_KR.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/pl_PL.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/pt_BR.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/ru_RU.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/tr_TR.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/uk_UA.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/zh_CN.json | 656 ---- src/Ryujinx.Ava/Assets/Locales/zh_TW.json | 656 ---- src/Ryujinx.Ava/Assets/Styles/Styles.xaml | 396 --- src/Ryujinx.Ava/Assets/Styles/Themes.xaml | 85 - src/Ryujinx.Ava/Common/ApplicationHelper.cs | 419 --- src/Ryujinx.Ava/Common/ApplicationSort.cs | 15 - src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs | 16 - src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs | 40 - src/Ryujinx.Ava/Common/Locale/LocaleManager.cs | 160 - src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs | 203 -- src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs | 107 - .../Input/AvaloniaKeyboardMappingHelper.cs | 185 -- src/Ryujinx.Ava/Input/AvaloniaMouse.cs | 87 - src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs | 159 - src/Ryujinx.Ava/Modules/Updater/Updater.cs | 764 ----- src/Ryujinx.Ava/Program.cs | 237 -- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 167 - src/Ryujinx.Ava/Ryujinx.ico | Bin 108122 -> 0 bytes src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs | 204 -- .../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 162 - src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs | 41 - .../UI/Applet/ControllerAppletDialog.axaml | 145 - .../UI/Applet/ControllerAppletDialog.axaml.cs | 140 - src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 54 - .../UI/Applet/ErrorAppletWindow.axaml.cs | 74 - src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 67 - .../UI/Applet/SwkbdAppletDialog.axaml.cs | 183 -- .../UI/Controls/ApplicationContextMenu.axaml | 95 - .../UI/Controls/ApplicationContextMenu.axaml.cs | 371 --- .../UI/Controls/ApplicationGridView.axaml | 102 - .../UI/Controls/ApplicationGridView.axaml.cs | 51 - .../UI/Controls/ApplicationListView.axaml | 160 - .../UI/Controls/ApplicationListView.axaml.cs | 51 - .../UI/Controls/NavigationDialogHost.axaml | 17 - .../UI/Controls/NavigationDialogHost.axaml.cs | 217 -- src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs | 31 - src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml | 42 - .../UI/Controls/UpdateWaitWindow.axaml.cs | 31 - .../UI/Helpers/ApplicationOpenedEventArgs.cs | 16 - .../UI/Helpers/BitmapArrayValueConverter.cs | 36 - src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs | 118 - src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs | 425 --- src/Ryujinx.Ava/UI/Helpers/Glyph.cs | 9 - src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs | 42 - src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs | 46 - .../UI/Helpers/LocalizedNeverConverter.cs | 43 - src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs | 102 - src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs | 71 - src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs | 70 - src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs | 39 - src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs | 28 - src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs | 90 - src/Ryujinx.Ava/UI/Helpers/UserResult.cs | 12 - src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs | 125 - src/Ryujinx.Ava/UI/Models/CheatNode.cs | 57 - src/Ryujinx.Ava/UI/Models/ControllerModel.cs | 6 - src/Ryujinx.Ava/UI/Models/DeviceType.cs | 9 - .../UI/Models/DownloadableContentModel.cs | 35 - .../UI/Models/Generic/LastPlayedSortComparer.cs | 31 - .../UI/Models/Generic/TimePlayedSortComparer.cs | 31 - src/Ryujinx.Ava/UI/Models/InputConfiguration.cs | 456 --- src/Ryujinx.Ava/UI/Models/ModModel.cs | 32 - src/Ryujinx.Ava/UI/Models/PlayerModel.cs | 6 - src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs | 32 - src/Ryujinx.Ava/UI/Models/SaveModel.cs | 96 - .../UI/Models/StatusUpdatedEventArgs.cs | 28 - src/Ryujinx.Ava/UI/Models/TempProfile.cs | 61 - src/Ryujinx.Ava/UI/Models/TimeZone.cs | 16 - src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs | 19 - src/Ryujinx.Ava/UI/Models/UserProfile.cs | 104 - src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs | 294 -- .../UI/Renderer/EmbeddedWindowOpenGL.cs | 94 - .../UI/Renderer/EmbeddedWindowVulkan.cs | 42 - .../UI/Renderer/OpenTKBindingsContext.cs | 20 - src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml | 12 - src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs | 68 - src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs | 49 - .../UI/ViewModels/AboutWindowViewModel.cs | 131 - .../UI/ViewModels/AmiiboWindowViewModel.cs | 518 ---- src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs | 15 - .../UI/ViewModels/ControllerInputViewModel.cs | 897 ------ .../DownloadableContentManagerViewModel.cs | 340 --- .../UI/ViewModels/MainWindowViewModel.cs | 1708 ----------- .../UI/ViewModels/ModManagerViewModel.cs | 336 -- .../UI/ViewModels/MotionInputViewModel.cs | 93 - .../UI/ViewModels/RumbleInputViewModel.cs | 27 - src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs | 614 ---- .../UI/ViewModels/TitleUpdateViewModel.cs | 249 -- .../UserFirmwareAvatarSelectorViewModel.cs | 222 -- .../UserProfileImageSelectorViewModel.cs | 18 - .../UI/ViewModels/UserProfileViewModel.cs | 28 - .../UI/ViewModels/UserSaveManagerViewModel.cs | 117 - .../UI/Views/Input/ControllerInputView.axaml | 1130 ------- .../UI/Views/Input/ControllerInputView.axaml.cs | 181 -- .../UI/Views/Input/MotionInputView.axaml | 171 -- .../UI/Views/Input/MotionInputView.axaml.cs | 68 - .../UI/Views/Input/RumbleInputView.axaml | 62 - .../UI/Views/Input/RumbleInputView.axaml.cs | 58 - .../UI/Views/Main/MainMenuBarView.axaml | 203 -- .../UI/Views/Main/MainMenuBarView.axaml.cs | 232 -- .../UI/Views/Main/MainStatusBarView.axaml | 289 -- .../UI/Views/Main/MainStatusBarView.axaml.cs | 72 - .../UI/Views/Main/MainViewControls.axaml | 177 -- .../UI/Views/Main/MainViewControls.axaml.cs | 54 - .../UI/Views/Settings/SettingsAudioView.axaml | 81 - .../UI/Views/Settings/SettingsAudioView.axaml.cs | 12 - .../UI/Views/Settings/SettingsCPUView.axaml | 77 - .../UI/Views/Settings/SettingsCPUView.axaml.cs | 12 - .../UI/Views/Settings/SettingsGraphicsView.axaml | 301 -- .../Views/Settings/SettingsGraphicsView.axaml.cs | 12 - .../UI/Views/Settings/SettingsHotkeysView.axaml | 103 - .../UI/Views/Settings/SettingsHotkeysView.axaml.cs | 81 - .../UI/Views/Settings/SettingsInputView.axaml | 67 - .../UI/Views/Settings/SettingsInputView.axaml.cs | 17 - .../UI/Views/Settings/SettingsLoggingView.axaml | 120 - .../UI/Views/Settings/SettingsLoggingView.axaml.cs | 12 - .../UI/Views/Settings/SettingsNetworkView.axaml | 58 - .../UI/Views/Settings/SettingsNetworkView.axaml.cs | 12 - .../UI/Views/Settings/SettingsSystemView.axaml | 224 -- .../UI/Views/Settings/SettingsSystemView.axaml.cs | 37 - .../UI/Views/Settings/SettingsUIView.axaml | 128 - .../UI/Views/Settings/SettingsUIView.axaml.cs | 65 - src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml | 122 - .../UI/Views/User/UserEditorView.axaml.cs | 165 - .../User/UserFirmwareAvatarSelectorView.axaml | 113 - .../User/UserFirmwareAvatarSelectorView.axaml.cs | 89 - .../Views/User/UserProfileImageSelectorView.axaml | 62 - .../User/UserProfileImageSelectorView.axaml.cs | 116 - .../UI/Views/User/UserRecovererView.axaml | 82 - .../UI/Views/User/UserRecovererView.axaml.cs | 51 - .../UI/Views/User/UserSaveManagerView.axaml | 213 -- .../UI/Views/User/UserSaveManagerView.axaml.cs | 148 - .../UI/Views/User/UserSelectorView.axaml | 162 - .../UI/Views/User/UserSelectorView.axaml.cs | 129 - src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 270 -- src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs | 63 - src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml | 75 - src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs | 60 - src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml | 126 - src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs | 123 - .../UI/Windows/ContentDialogOverlayWindow.axaml | 25 - .../UI/Windows/ContentDialogOverlayWindow.axaml.cs | 21 - .../Windows/DownloadableContentManagerWindow.axaml | 192 -- .../DownloadableContentManagerWindow.axaml.cs | 115 - src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs | 194 -- src/Ryujinx.Ava/UI/Windows/MainWindow.axaml | 205 -- src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs | 551 ---- src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml | 179 -- .../UI/Windows/ModManagerWindow.axaml.cs | 139 - src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml | 130 - src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs | 103 - src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs | 44 - src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml | 133 - .../UI/Windows/TitleUpdateWindow.axaml.cs | 96 - src/Ryujinx.Ava/app.manifest | 10 - src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs | 205 ++ src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs | 94 + src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs | 178 ++ src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs | 90 + src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs | 108 + src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs | 95 + .../Modules/Updater/UpdateDialog.glade | 127 + src/Ryujinx.Gtk3/Modules/Updater/Updater.cs | 622 ++++ src/Ryujinx.Gtk3/Program.cs | 378 +++ src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj | 104 + src/Ryujinx.Gtk3/Ryujinx.ico | Bin 0 -> 108122 bytes src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs | 31 + .../UI/Applet/GtkDynamicTextInputHandler.cs | 108 + src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs | 200 ++ src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs | 90 + src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs | 127 + src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs | 135 + src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs | 33 + src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs | 36 + src/Ryujinx.Gtk3/UI/MainWindow.cs | 1941 ++++++++++++ src/Ryujinx.Gtk3/UI/MainWindow.glade | 1006 ++++++ src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs | 142 + src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs | 20 + src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs | 803 +++++ src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs | 49 + src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs | 28 + src/Ryujinx.Gtk3/UI/VulkanRenderer.cs | 93 + .../UI/Widgets/GameTableContextMenu.Designer.cs | 233 ++ .../UI/Widgets/GameTableContextMenu.cs | 644 ++++ src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs | 114 + src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs | 37 + src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs | 57 + src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade | 124 + src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs | 27 + src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs | 123 + .../UI/Windows/AboutWindow.Designer.cs | 511 ++++ src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs | 85 + .../UI/Windows/AmiiboWindow.Designer.cs | 190 ++ src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs | 438 +++ src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs | 291 ++ src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs | 156 + src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade | 150 + src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs | 1230 ++++++++ src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade | 2241 ++++++++++++++ src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs | 280 ++ src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade | 202 ++ src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs | 847 +++++ src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade | 3221 ++++++++++++++++++++ src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs | 206 ++ .../UI/Windows/TitleUpdateWindow.glade | 214 ++ .../Windows/UserProfilesManagerWindow.Designer.cs | 255 ++ .../UI/Windows/UserProfilesManagerWindow.cs | 328 ++ src/Ryujinx/App.axaml | 17 + src/Ryujinx/App.axaml.cs | 115 + src/Ryujinx/AppHost.cs | 1195 ++++++++ src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf | Bin 0 -> 408752 bytes src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg | 155 + src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg | 341 +++ .../Assets/Icons/Controller_JoyConRight.svg | 185 ++ src/Ryujinx/Assets/Icons/Controller_ProCon.svg | 84 + src/Ryujinx/Assets/Locales/de_DE.json | 656 ++++ src/Ryujinx/Assets/Locales/el_GR.json | 656 ++++ src/Ryujinx/Assets/Locales/en_US.json | 668 ++++ src/Ryujinx/Assets/Locales/es_ES.json | 656 ++++ src/Ryujinx/Assets/Locales/fr_FR.json | 656 ++++ src/Ryujinx/Assets/Locales/he_IL.json | 656 ++++ src/Ryujinx/Assets/Locales/it_IT.json | 656 ++++ src/Ryujinx/Assets/Locales/ja_JP.json | 656 ++++ src/Ryujinx/Assets/Locales/ko_KR.json | 656 ++++ src/Ryujinx/Assets/Locales/pl_PL.json | 656 ++++ src/Ryujinx/Assets/Locales/pt_BR.json | 656 ++++ src/Ryujinx/Assets/Locales/ru_RU.json | 656 ++++ src/Ryujinx/Assets/Locales/tr_TR.json | 656 ++++ src/Ryujinx/Assets/Locales/uk_UA.json | 656 ++++ src/Ryujinx/Assets/Locales/zh_CN.json | 656 ++++ src/Ryujinx/Assets/Locales/zh_TW.json | 656 ++++ src/Ryujinx/Assets/Styles/Styles.xaml | 396 +++ src/Ryujinx/Assets/Styles/Themes.xaml | 85 + src/Ryujinx/Common/ApplicationHelper.cs | 419 +++ src/Ryujinx/Common/ApplicationSort.cs | 15 + src/Ryujinx/Common/KeyboardHotkeyState.cs | 16 + src/Ryujinx/Common/Locale/LocaleExtension.cs | 40 + src/Ryujinx/Common/Locale/LocaleManager.cs | 160 + src/Ryujinx/Input/AvaloniaKeyboard.cs | 203 ++ src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 107 + src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs | 185 ++ src/Ryujinx/Input/AvaloniaMouse.cs | 87 + src/Ryujinx/Input/AvaloniaMouseDriver.cs | 159 + src/Ryujinx/Input/GTK3/GTK3Keyboard.cs | 205 -- src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs | 94 - src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs | 178 -- src/Ryujinx/Input/GTK3/GTK3Mouse.cs | 90 - src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs | 108 - src/Ryujinx/Modules/Updater/UpdateDialog.cs | 95 - src/Ryujinx/Modules/Updater/UpdateDialog.glade | 127 - src/Ryujinx/Modules/Updater/Updater.cs | 480 ++- src/Ryujinx/Program.cs | 309 +- src/Ryujinx/Ryujinx.csproj | 130 +- src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 204 ++ .../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 162 + src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs | 41 + src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml | 145 + .../UI/Applet/ControllerAppletDialog.axaml.cs | 140 + src/Ryujinx/UI/Applet/ErrorAppletDialog.cs | 31 - src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml | 54 + src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs | 74 + .../UI/Applet/GtkDynamicTextInputHandler.cs | 108 - src/Ryujinx/UI/Applet/GtkHostUIHandler.cs | 200 -- src/Ryujinx/UI/Applet/GtkHostUITheme.cs | 90 - src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml | 67 + src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs | 183 ++ src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs | 127 - .../UI/Controls/ApplicationContextMenu.axaml | 95 + .../UI/Controls/ApplicationContextMenu.axaml.cs | 371 +++ src/Ryujinx/UI/Controls/ApplicationGridView.axaml | 102 + .../UI/Controls/ApplicationGridView.axaml.cs | 51 + src/Ryujinx/UI/Controls/ApplicationListView.axaml | 160 + .../UI/Controls/ApplicationListView.axaml.cs | 51 + src/Ryujinx/UI/Controls/NavigationDialogHost.axaml | 17 + .../UI/Controls/NavigationDialogHost.axaml.cs | 217 ++ src/Ryujinx/UI/Controls/SliderScroll.axaml.cs | 31 + src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml | 42 + src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs | 31 + src/Ryujinx/UI/Helper/MetalHelper.cs | 135 - src/Ryujinx/UI/Helper/SortHelper.cs | 33 - src/Ryujinx/UI/Helper/ThemeHelper.cs | 36 - .../UI/Helpers/ApplicationOpenedEventArgs.cs | 16 + .../UI/Helpers/BitmapArrayValueConverter.cs | 36 + src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs | 118 + src/Ryujinx/UI/Helpers/ContentDialogHelper.cs | 425 +++ src/Ryujinx/UI/Helpers/Glyph.cs | 9 + src/Ryujinx/UI/Helpers/GlyphValueConverter.cs | 42 + src/Ryujinx/UI/Helpers/KeyValueConverter.cs | 46 + src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs | 43 + src/Ryujinx/UI/Helpers/LoggerAdapter.cs | 102 + src/Ryujinx/UI/Helpers/MiniCommand.cs | 71 + src/Ryujinx/UI/Helpers/NotificationHelper.cs | 70 + src/Ryujinx/UI/Helpers/OffscreenTextBox.cs | 39 + src/Ryujinx/UI/Helpers/TimeZoneConverter.cs | 28 + src/Ryujinx/UI/Helpers/UserErrorDialog.cs | 90 + src/Ryujinx/UI/Helpers/UserResult.cs | 12 + src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 125 + src/Ryujinx/UI/MainWindow.cs | 1941 ------------ src/Ryujinx/UI/MainWindow.glade | 1006 ------ src/Ryujinx/UI/Models/CheatNode.cs | 57 + src/Ryujinx/UI/Models/ControllerModel.cs | 6 + src/Ryujinx/UI/Models/DeviceType.cs | 9 + src/Ryujinx/UI/Models/DownloadableContentModel.cs | 35 + .../UI/Models/Generic/LastPlayedSortComparer.cs | 31 + .../UI/Models/Generic/TimePlayedSortComparer.cs | 31 + src/Ryujinx/UI/Models/InputConfiguration.cs | 456 +++ src/Ryujinx/UI/Models/ModModel.cs | 32 + src/Ryujinx/UI/Models/PlayerModel.cs | 6 + src/Ryujinx/UI/Models/ProfileImageModel.cs | 32 + src/Ryujinx/UI/Models/SaveModel.cs | 96 + src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs | 28 + src/Ryujinx/UI/Models/TempProfile.cs | 61 + src/Ryujinx/UI/Models/TimeZone.cs | 16 + src/Ryujinx/UI/Models/TitleUpdateModel.cs | 19 + src/Ryujinx/UI/Models/UserProfile.cs | 104 + src/Ryujinx/UI/OpenGLRenderer.cs | 142 - src/Ryujinx/UI/OpenToolkitBindingsContext.cs | 20 - src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 294 ++ src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs | 94 + src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs | 42 + src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs | 20 + src/Ryujinx/UI/Renderer/RendererHost.axaml | 12 + src/Ryujinx/UI/Renderer/RendererHost.axaml.cs | 68 + src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs | 49 + src/Ryujinx/UI/RendererWidgetBase.cs | 803 ----- src/Ryujinx/UI/SPBOpenGLContext.cs | 49 - src/Ryujinx/UI/StatusUpdatedEventArgs.cs | 28 - src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs | 131 + src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs | 518 ++++ src/Ryujinx/UI/ViewModels/BaseModel.cs | 15 + .../UI/ViewModels/ControllerInputViewModel.cs | 897 ++++++ .../DownloadableContentManagerViewModel.cs | 340 +++ src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 1708 +++++++++++ src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs | 336 ++ src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs | 93 + src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs | 27 + src/Ryujinx/UI/ViewModels/SettingsViewModel.cs | 614 ++++ src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs | 249 ++ .../UserFirmwareAvatarSelectorViewModel.cs | 222 ++ .../UserProfileImageSelectorViewModel.cs | 18 + src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs | 28 + .../UI/ViewModels/UserSaveManagerViewModel.cs | 117 + .../UI/Views/Input/ControllerInputView.axaml | 1130 +++++++ .../UI/Views/Input/ControllerInputView.axaml.cs | 181 ++ src/Ryujinx/UI/Views/Input/MotionInputView.axaml | 171 ++ .../UI/Views/Input/MotionInputView.axaml.cs | 68 + src/Ryujinx/UI/Views/Input/RumbleInputView.axaml | 62 + .../UI/Views/Input/RumbleInputView.axaml.cs | 58 + src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml | 203 ++ src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs | 232 ++ src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml | 289 ++ .../UI/Views/Main/MainStatusBarView.axaml.cs | 72 + src/Ryujinx/UI/Views/Main/MainViewControls.axaml | 177 ++ .../UI/Views/Main/MainViewControls.axaml.cs | 54 + .../UI/Views/Settings/SettingsAudioView.axaml | 81 + .../UI/Views/Settings/SettingsAudioView.axaml.cs | 12 + .../UI/Views/Settings/SettingsCPUView.axaml | 77 + .../UI/Views/Settings/SettingsCPUView.axaml.cs | 12 + .../UI/Views/Settings/SettingsGraphicsView.axaml | 301 ++ .../Views/Settings/SettingsGraphicsView.axaml.cs | 12 + .../UI/Views/Settings/SettingsHotkeysView.axaml | 103 + .../UI/Views/Settings/SettingsHotkeysView.axaml.cs | 81 + .../UI/Views/Settings/SettingsInputView.axaml | 67 + .../UI/Views/Settings/SettingsInputView.axaml.cs | 17 + .../UI/Views/Settings/SettingsLoggingView.axaml | 120 + .../UI/Views/Settings/SettingsLoggingView.axaml.cs | 12 + .../UI/Views/Settings/SettingsNetworkView.axaml | 58 + .../UI/Views/Settings/SettingsNetworkView.axaml.cs | 12 + .../UI/Views/Settings/SettingsSystemView.axaml | 224 ++ .../UI/Views/Settings/SettingsSystemView.axaml.cs | 37 + src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml | 128 + .../UI/Views/Settings/SettingsUIView.axaml.cs | 65 + src/Ryujinx/UI/Views/User/UserEditorView.axaml | 122 + src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs | 165 + .../User/UserFirmwareAvatarSelectorView.axaml | 113 + .../User/UserFirmwareAvatarSelectorView.axaml.cs | 89 + .../Views/User/UserProfileImageSelectorView.axaml | 62 + .../User/UserProfileImageSelectorView.axaml.cs | 116 + src/Ryujinx/UI/Views/User/UserRecovererView.axaml | 82 + .../UI/Views/User/UserRecovererView.axaml.cs | 51 + .../UI/Views/User/UserSaveManagerView.axaml | 213 ++ .../UI/Views/User/UserSaveManagerView.axaml.cs | 148 + src/Ryujinx/UI/Views/User/UserSelectorView.axaml | 162 + .../UI/Views/User/UserSelectorView.axaml.cs | 129 + 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.axaml | 270 ++ src/Ryujinx/UI/Windows/AboutWindow.axaml.cs | 63 + src/Ryujinx/UI/Windows/AboutWindow.cs | 85 - src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs | 190 -- src/Ryujinx/UI/Windows/AmiiboWindow.axaml | 75 + src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs | 60 + src/Ryujinx/UI/Windows/AmiiboWindow.cs | 438 --- src/Ryujinx/UI/Windows/AvatarWindow.cs | 291 -- src/Ryujinx/UI/Windows/CheatWindow.axaml | 126 + src/Ryujinx/UI/Windows/CheatWindow.axaml.cs | 123 + src/Ryujinx/UI/Windows/CheatWindow.cs | 156 - src/Ryujinx/UI/Windows/CheatWindow.glade | 150 - .../UI/Windows/ContentDialogOverlayWindow.axaml | 25 + .../UI/Windows/ContentDialogOverlayWindow.axaml.cs | 21 + 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 -- .../Windows/DownloadableContentManagerWindow.axaml | 192 ++ .../DownloadableContentManagerWindow.axaml.cs | 115 + src/Ryujinx/UI/Windows/IconColorPicker.cs | 194 ++ src/Ryujinx/UI/Windows/MainWindow.axaml | 205 ++ src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 551 ++++ src/Ryujinx/UI/Windows/ModManagerWindow.axaml | 179 ++ src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs | 139 + src/Ryujinx/UI/Windows/SettingsWindow.axaml | 130 + src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs | 103 + src/Ryujinx/UI/Windows/SettingsWindow.cs | 847 ----- src/Ryujinx/UI/Windows/SettingsWindow.glade | 3221 -------------------- src/Ryujinx/UI/Windows/StyleableWindow.cs | 44 + src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml | 133 + src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs | 96 + 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/app.manifest | 10 + 453 files changed, 52853 insertions(+), 52854 deletions(-) delete mode 100644 src/Ryujinx.Ava/App.axaml delete mode 100644 src/Ryujinx.Ava/App.axaml.cs delete mode 100644 src/Ryujinx.Ava/AppHost.cs delete mode 100644 src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf delete mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg delete mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg delete mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg delete mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg delete mode 100644 src/Ryujinx.Ava/Assets/Locales/de_DE.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/el_GR.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/en_US.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/es_ES.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/fr_FR.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/he_IL.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/it_IT.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/ja_JP.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/ko_KR.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/pl_PL.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/pt_BR.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/ru_RU.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/tr_TR.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/uk_UA.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/zh_CN.json delete mode 100644 src/Ryujinx.Ava/Assets/Locales/zh_TW.json delete mode 100644 src/Ryujinx.Ava/Assets/Styles/Styles.xaml delete mode 100644 src/Ryujinx.Ava/Assets/Styles/Themes.xaml delete mode 100644 src/Ryujinx.Ava/Common/ApplicationHelper.cs delete mode 100644 src/Ryujinx.Ava/Common/ApplicationSort.cs delete mode 100644 src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs delete mode 100644 src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs delete mode 100644 src/Ryujinx.Ava/Common/Locale/LocaleManager.cs delete mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs delete mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs delete mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs delete mode 100644 src/Ryujinx.Ava/Input/AvaloniaMouse.cs delete mode 100644 src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs delete mode 100644 src/Ryujinx.Ava/Modules/Updater/Updater.cs delete mode 100644 src/Ryujinx.Ava/Program.cs delete mode 100644 src/Ryujinx.Ava/Ryujinx.Ava.csproj delete mode 100644 src/Ryujinx.Ava/Ryujinx.ico delete mode 100644 src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs delete mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs delete mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs delete mode 100644 src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml delete mode 100644 src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml delete mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml delete mode 100644 src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/Glyph.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/UserResult.cs delete mode 100644 src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/CheatNode.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/ControllerModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/DeviceType.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/InputConfiguration.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/ModModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/PlayerModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/SaveModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/TempProfile.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/TimeZone.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Models/UserProfile.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml delete mode 100644 src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml delete mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/MainWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs delete mode 100644 src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml delete mode 100644 src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs delete mode 100644 src/Ryujinx.Ava/app.manifest create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/Updater.cs create mode 100644 src/Ryujinx.Gtk3/Program.cs create mode 100644 src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj create mode 100644 src/Ryujinx.Gtk3/Ryujinx.ico create mode 100644 src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/MainWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/MainWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs create mode 100644 src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs create mode 100644 src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs create mode 100644 src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs create mode 100644 src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Gtk3/UI/VulkanRenderer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs create mode 100644 src/Ryujinx/App.axaml create mode 100644 src/Ryujinx/App.axaml.cs create mode 100644 src/Ryujinx/AppHost.cs create mode 100644 src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_ProCon.svg create mode 100644 src/Ryujinx/Assets/Locales/de_DE.json create mode 100644 src/Ryujinx/Assets/Locales/el_GR.json create mode 100644 src/Ryujinx/Assets/Locales/en_US.json create mode 100644 src/Ryujinx/Assets/Locales/es_ES.json create mode 100644 src/Ryujinx/Assets/Locales/fr_FR.json create mode 100644 src/Ryujinx/Assets/Locales/he_IL.json create mode 100644 src/Ryujinx/Assets/Locales/it_IT.json create mode 100644 src/Ryujinx/Assets/Locales/ja_JP.json create mode 100644 src/Ryujinx/Assets/Locales/ko_KR.json create mode 100644 src/Ryujinx/Assets/Locales/pl_PL.json create mode 100644 src/Ryujinx/Assets/Locales/pt_BR.json create mode 100644 src/Ryujinx/Assets/Locales/ru_RU.json create mode 100644 src/Ryujinx/Assets/Locales/tr_TR.json create mode 100644 src/Ryujinx/Assets/Locales/uk_UA.json create mode 100644 src/Ryujinx/Assets/Locales/zh_CN.json create mode 100644 src/Ryujinx/Assets/Locales/zh_TW.json create mode 100644 src/Ryujinx/Assets/Styles/Styles.xaml create mode 100644 src/Ryujinx/Assets/Styles/Themes.xaml create mode 100644 src/Ryujinx/Common/ApplicationHelper.cs create mode 100644 src/Ryujinx/Common/ApplicationSort.cs create mode 100644 src/Ryujinx/Common/KeyboardHotkeyState.cs create mode 100644 src/Ryujinx/Common/Locale/LocaleExtension.cs create mode 100644 src/Ryujinx/Common/Locale/LocaleManager.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboard.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboardDriver.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs create mode 100644 src/Ryujinx/Input/AvaloniaMouse.cs create mode 100644 src/Ryujinx/Input/AvaloniaMouseDriver.cs delete mode 100644 src/Ryujinx/Input/GTK3/GTK3Keyboard.cs delete mode 100644 src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs delete mode 100644 src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs delete mode 100644 src/Ryujinx/Input/GTK3/GTK3Mouse.cs delete mode 100644 src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs delete mode 100644 src/Ryujinx/Modules/Updater/UpdateDialog.cs delete mode 100644 src/Ryujinx/Modules/Updater/UpdateDialog.glade create mode 100644 src/Ryujinx/UI/Applet/AvaHostUIHandler.cs create mode 100644 src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs create mode 100644 src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs create mode 100644 src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs delete mode 100644 src/Ryujinx/UI/Applet/ErrorAppletDialog.cs create mode 100644 src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml create mode 100644 src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.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 create mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs delete mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationGridView.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationListView.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/NavigationDialogHost.axaml create mode 100644 src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/SliderScroll.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml create mode 100644 src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.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 create mode 100644 src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs create mode 100644 src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs create mode 100644 src/Ryujinx/UI/Helpers/ContentDialogHelper.cs create mode 100644 src/Ryujinx/UI/Helpers/Glyph.cs create mode 100644 src/Ryujinx/UI/Helpers/GlyphValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/KeyValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/LoggerAdapter.cs create mode 100644 src/Ryujinx/UI/Helpers/MiniCommand.cs create mode 100644 src/Ryujinx/UI/Helpers/NotificationHelper.cs create mode 100644 src/Ryujinx/UI/Helpers/OffscreenTextBox.cs create mode 100644 src/Ryujinx/UI/Helpers/TimeZoneConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/UserErrorDialog.cs create mode 100644 src/Ryujinx/UI/Helpers/UserResult.cs create mode 100644 src/Ryujinx/UI/Helpers/Win32NativeInterop.cs delete mode 100644 src/Ryujinx/UI/MainWindow.cs delete mode 100644 src/Ryujinx/UI/MainWindow.glade create mode 100644 src/Ryujinx/UI/Models/CheatNode.cs create mode 100644 src/Ryujinx/UI/Models/ControllerModel.cs create mode 100644 src/Ryujinx/UI/Models/DeviceType.cs create mode 100644 src/Ryujinx/UI/Models/DownloadableContentModel.cs create mode 100644 src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs create mode 100644 src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs create mode 100644 src/Ryujinx/UI/Models/InputConfiguration.cs create mode 100644 src/Ryujinx/UI/Models/ModModel.cs create mode 100644 src/Ryujinx/UI/Models/PlayerModel.cs create mode 100644 src/Ryujinx/UI/Models/ProfileImageModel.cs create mode 100644 src/Ryujinx/UI/Models/SaveModel.cs create mode 100644 src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx/UI/Models/TempProfile.cs create mode 100644 src/Ryujinx/UI/Models/TimeZone.cs create mode 100644 src/Ryujinx/UI/Models/TitleUpdateModel.cs create mode 100644 src/Ryujinx/UI/Models/UserProfile.cs delete mode 100644 src/Ryujinx/UI/OpenGLRenderer.cs delete mode 100644 src/Ryujinx/UI/OpenToolkitBindingsContext.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindow.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs create mode 100644 src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs create mode 100644 src/Ryujinx/UI/Renderer/RendererHost.axaml create mode 100644 src/Ryujinx/UI/Renderer/RendererHost.axaml.cs create mode 100644 src/Ryujinx/UI/Renderer/SPBOpenGLContext.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 create mode 100644 src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/BaseModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/SettingsViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs create mode 100644 src/Ryujinx/UI/Views/Input/ControllerInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/MotionInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/RumbleInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainViewControls.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserEditorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserRecovererView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserSelectorView.axaml.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 create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.axaml.cs delete mode 100644 src/Ryujinx/UI/Windows/AboutWindow.cs delete mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs delete mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.cs delete mode 100644 src/Ryujinx/UI/Windows/AvatarWindow.cs create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.axaml.cs delete mode 100644 src/Ryujinx/UI/Windows/CheatWindow.cs delete mode 100644 src/Ryujinx/UI/Windows/CheatWindow.glade create mode 100644 src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs 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 create mode 100644 src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/IconColorPicker.cs create mode 100644 src/Ryujinx/UI/Windows/MainWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/MainWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/ModManagerWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs delete mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.cs delete mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.glade create mode 100644 src/Ryujinx/UI/Windows/StyleableWindow.cs create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs 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 create mode 100644 src/Ryujinx/app.manifest (limited to 'src') diff --git a/src/Ryujinx.Ava/App.axaml b/src/Ryujinx.Ava/App.axaml deleted file mode 100644 index eab318b7..00000000 --- a/src/Ryujinx.Ava/App.axaml +++ /dev/null @@ -1,17 +0,0 @@ -<Application - x:Class="Ryujinx.Ava.App" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:sty="using:FluentAvalonia.Styling"> - <Application.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <MergeResourceInclude Source="/Assets/Styles/Themes.xaml"/> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </Application.Resources> - <Application.Styles> - <sty:FluentAvaloniaTheme PreferSystemTheme="False" /> - <StyleInclude Source="/Assets/Styles/Styles.xaml"/> - </Application.Styles> -</Application> \ No newline at end of file diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs deleted file mode 100644 index 387a6dc1..00000000 --- a/src/Ryujinx.Ava/App.axaml.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; -using Avalonia.Styling; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -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 System; -using System.Diagnostics; - -namespace Ryujinx.Ava -{ - public class App : Application - { - public override void Initialize() - { - Name = $"Ryujinx {Program.Version}"; - - AvaloniaXamlLoader.Load(this); - - if (OperatingSystem.IsMacOS()) - { - Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false"); - } - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new MainWindow(); - } - - base.OnFrameworkInitializationCompleted(); - - if (Program.PreviewerDetached) - { - ApplyConfiguredTheme(); - - ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; - ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event; - ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event; - } - } - - private void CustomThemeChanged_Event(object sender, ReactiveEventArgs<bool> e) - { - ApplyConfiguredTheme(); - } - - private void ShowRestartDialog() - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Dispatcher.UIThread.InvokeAsync(async () => - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - var result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage], - LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]); - - if (result == UserResult.Yes) - { - var path = Environment.ProcessPath; - var proc = Process.Start(path, CommandLineState.Arguments); - desktop.Shutdown(); - Environment.Exit(0); - } - } - }); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - - private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e) - { - ApplyConfiguredTheme(); - } - - private void ApplyConfiguredTheme() - { - try - { - string baseStyle = ConfigurationState.Instance.UI.BaseStyle; - - if (string.IsNullOrWhiteSpace(baseStyle)) - { - ConfigurationState.Instance.UI.BaseStyle.Value = "Dark"; - - baseStyle = ConfigurationState.Instance.UI.BaseStyle; - } - - RequestedThemeVariant = baseStyle switch - { - "Light" => ThemeVariant.Light, - "Dark" => ThemeVariant.Dark, - _ => ThemeVariant.Default, - }; - } - catch (Exception) - { - Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); - - ShowRestartDialog(); - } - } - } -} diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs deleted file mode 100644 index 04cec957..00000000 --- a/src/Ryujinx.Ava/AppHost.cs +++ /dev/null @@ -1,1195 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input; -using Avalonia.Threading; -using LibHac.Tools.FsSystem; -using Ryujinx.Audio.Backends.Dummy; -using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; -using Ryujinx.Audio.Backends.SoundIo; -using Ryujinx.Audio.Integration; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.Renderer; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Multiplayer; -using Ryujinx.Common.Logging; -using Ryujinx.Common.SystemInterop; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.GAL.Multithreading; -using Ryujinx.Graphics.Gpu; -using Ryujinx.Graphics.OpenGL; -using Ryujinx.Graphics.Vulkan; -using Ryujinx.HLE; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -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 Silk.NET.Vulkan; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SPB.Graphics.Vulkan; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; -using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; -using Image = SixLabors.ImageSharp.Image; -using InputManager = Ryujinx.Input.HLE.InputManager; -using IRenderer = Ryujinx.Graphics.GAL.IRenderer; -using Key = Ryujinx.Input.Key; -using MouseButton = Ryujinx.Input.MouseButton; -using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; -using Size = Avalonia.Size; -using Switch = Ryujinx.HLE.Switch; - -namespace Ryujinx.Ava -{ - internal class AppHost - { - private const int CursorHideIdleTime = 5; // Hide Cursor seconds. - private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. - private const int TargetFps = 60; - private const float VolumeDelta = 0.05f; - - private static readonly Cursor _invisibleCursor = new(StandardCursorType.None); - private readonly IntPtr _invisibleCursorWin; - private readonly IntPtr _defaultCursorWin; - - private readonly long _ticksPerFrame; - private readonly Stopwatch _chrono; - private long _ticks; - - private readonly AccountManager _accountManager; - private readonly UserChannelPersistence _userChannelPersistence; - private readonly InputManager _inputManager; - - private readonly MainWindowViewModel _viewModel; - private readonly IKeyboard _keyboardInterface; - private readonly TopLevel _topLevel; - public RendererHost RendererHost; - - private readonly GraphicsDebugLevel _glLogLevel; - private float _newVolume; - private KeyboardHotkeyState _prevHotkeyState; - - private long _lastCursorMoveTime; - private bool _isCursorInRenderer = true; - - private bool _isStopped; - private bool _isActive; - private bool _renderingStarted; - - private readonly ManualResetEvent _gpuDoneEvent; - - private IRenderer _renderer; - private readonly Thread _renderingThread; - private readonly CancellationTokenSource _gpuCancellationTokenSource; - private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; - - private bool _dialogShown; - private readonly bool _isFirmwareTitle; - - private readonly object _lockObject = new(); - - public event EventHandler AppExit; - public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; - - public VirtualFileSystem VirtualFileSystem { get; } - public ContentManager ContentManager { get; } - public NpadManager NpadManager { get; } - public TouchScreenManager TouchScreenManager { get; } - public Switch Device { get; set; } - - public int Width { get; private set; } - public int Height { get; private set; } - public string ApplicationPath { get; private set; } - public bool ScreenshotRequested { get; set; } - - public AppHost( - RendererHost renderer, - InputManager inputManager, - string applicationPath, - VirtualFileSystem virtualFileSystem, - ContentManager contentManager, - AccountManager accountManager, - UserChannelPersistence userChannelPersistence, - MainWindowViewModel viewmodel, - TopLevel topLevel) - { - _viewModel = viewmodel; - _inputManager = inputManager; - _accountManager = accountManager; - _userChannelPersistence = userChannelPersistence; - _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" }; - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; - _topLevel = topLevel; - - _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer)); - - _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); - - NpadManager = _inputManager.CreateNpadManager(); - TouchScreenManager = _inputManager.CreateTouchScreenManager(); - ApplicationPath = applicationPath; - VirtualFileSystem = virtualFileSystem; - ContentManager = contentManager; - - RendererHost = renderer; - - _chrono = new Stopwatch(); - _ticksPerFrame = Stopwatch.Frequency / TargetFps; - - if (ApplicationPath.StartsWith("@SystemContent")) - { - ApplicationPath = VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); - - _isFirmwareTitle = true; - } - - ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed; - - _topLevel.PointerMoved += TopLevel_PointerEnteredOrMoved; - _topLevel.PointerEntered += TopLevel_PointerEnteredOrMoved; - _topLevel.PointerExited += TopLevel_PointerExited; - - if (OperatingSystem.IsWindows()) - { - _invisibleCursorWin = CreateEmptyCursor(); - _defaultCursorWin = CreateArrowCursor(); - } - - ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; - ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; - ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; - ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; - ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; - ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; - ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing; - ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; - ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; - ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; - - ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState; - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; - ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; - - _gpuCancellationTokenSource = new CancellationTokenSource(); - _gpuDoneEvent = new ManualResetEvent(false); - } - - private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) - { - if (sender is MainWindow window) - { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - - var point = e.GetCurrentPoint(window).Position; - var bounds = RendererHost.EmbeddedWindow.Bounds; - - _isCursorInRenderer = point.X >= bounds.X && - point.X <= bounds.Width + bounds.X && - point.Y >= bounds.Y && - point.Y <= bounds.Height + bounds.Y; - } - } - - private void TopLevel_PointerExited(object sender, PointerEventArgs e) - { - _isCursorInRenderer = false; - } - - private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e) - { - _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - } - - private void UpdateScalingFilter(object sender, ReactiveEventArgs<ScalingFilter> e) - { - _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - } - - private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs<bool> e) - { - _renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); - } - - private void ShowCursor() - { - Dispatcher.UIThread.Post(() => - { - _viewModel.Cursor = Cursor.Default; - - if (OperatingSystem.IsWindows()) - { - SetCursor(_defaultCursorWin); - } - }); - } - - private void HideCursor() - { - Dispatcher.UIThread.Post(() => - { - _viewModel.Cursor = _invisibleCursor; - - if (OperatingSystem.IsWindows()) - { - SetCursor(_invisibleCursorWin); - } - }); - } - - private void SetRendererWindowSize(Size size) - { - if (_renderer != null) - { - double scale = _topLevel.RenderScaling; - - _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); - } - } - - private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) - { - if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) - { - Task.Run(() => - { - lock (_lockObject) - { - DateTime currentTime = DateTime.Now; - string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; - - string directory = AppDataManager.Mode switch - { - AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), - _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), - }; - - string path = Path.Combine(directory, filename); - - try - { - Directory.CreateDirectory(directory); - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); - - return; - } - - Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) - : Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); - - if (e.FlipX) - { - image.Mutate(x => x.Flip(FlipMode.Horizontal)); - } - - if (e.FlipY) - { - image.Mutate(x => x.Flip(FlipMode.Vertical)); - } - - image.SaveAsPng(path, new PngEncoder - { - ColorType = PngColorType.Rgb, - }); - - image.Dispose(); - - Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); - } - }); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); - } - } - - public void Start() - { - if (OperatingSystem.IsWindows()) - { - _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); - } - - DisplaySleep.Prevent(); - - NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); - TouchScreenManager.Initialize(Device); - - _viewModel.IsGameRunning = true; - - Dispatcher.UIThread.InvokeAsync(() => - { - _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version); - }); - - _viewModel.SetUiProgressHandlers(Device); - - RendererHost.BoundsChanged += Window_BoundsChanged; - - _isActive = true; - - _renderingThread.Start(); - - _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; - - MainLoop(); - - Exit(); - } - - private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args) - { - if (Device != null) - { - Device.Configuration.IgnoreMissingServices = args.NewValue; - } - } - - private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args) - { - if (Device != null) - { - Device.Configuration.AspectRatio = args.NewValue; - } - } - - private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e) - { - _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); - } - - private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e) - { - Device?.System.ChangeDockedModeState(e.NewValue); - } - - private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e) - { - Device?.SetVolume(e.NewValue); - - Dispatcher.UIThread.Post(() => - { - _viewModel.Volume = e.NewValue; - }); - } - - private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e) - { - Device.Configuration.EnableInternetAccess = e.NewValue; - } - - private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e) - { - Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; - } - - private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs<MultiplayerMode> e) - { - Device.Configuration.MultiplayerMode = e.NewValue; - } - - public void Stop() - { - _isActive = false; - } - - private void Exit() - { - (_keyboardInterface as AvaloniaKeyboard)?.Clear(); - - if (_isStopped) - { - return; - } - - _isStopped = true; - _isActive = false; - } - - public void DisposeContext() - { - Dispose(); - - _isActive = false; - - // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. - // We only need to wait for all commands submitted during the main gpu loop to be processed. - _gpuDoneEvent.WaitOne(); - _gpuDoneEvent.Dispose(); - - DisplaySleep.Restore(); - - NpadManager.Dispose(); - TouchScreenManager.Dispose(); - Device.Dispose(); - - DisposeGpu(); - - AppExit?.Invoke(this, EventArgs.Empty); - } - - private void Dispose() - { - if (Device.Processes != null) - { - MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); - } - - ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; - ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; - ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; - ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; - ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; - ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; - ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing; - ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough; - - _topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved; - _topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved; - _topLevel.PointerExited -= TopLevel_PointerExited; - - _gpuCancellationTokenSource.Cancel(); - _gpuCancellationTokenSource.Dispose(); - - _chrono.Stop(); - } - - public void DisposeGpu() - { - if (OperatingSystem.IsWindows()) - { - _windowsMultimediaTimerResolution?.Dispose(); - _windowsMultimediaTimerResolution = null; - } - - if (RendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow) - { - // Try to bind the OpenGL context before calling the shutdown event. - openGlWindow.MakeCurrent(false, false); - - Device.DisposeGpu(); - - // Unbind context and destroy everything. - openGlWindow.MakeCurrent(true, false); - } - else - { - Device.DisposeGpu(); - } - } - - private void HideCursorState_Changed(object sender, ReactiveEventArgs<HideCursorMode> state) - { - if (state.NewValue == HideCursorMode.OnIdle) - { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - } - } - - public async Task<bool> LoadGuestApplication() - { - InitializeSwitchInstance(); - MainWindow.UpdateGraphicsConfig(); - - SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); - - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) - { - { - if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) - { - if (userError == UserError.NoFirmware) - { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - ""); - - if (result != UserResult.Yes) - { - await UserErrorDialog.ShowUserErrorDialog(userError); - Device.Dispose(); - - return false; - } - } - - if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) - { - await UserErrorDialog.ShowUserErrorDialog(userError); - Device.Dispose(); - - return false; - } - - // Tell the user that we installed a firmware for them. - if (userError == UserError.NoFirmware) - { - firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); - - _viewModel.RefreshFirmwareStatus(); - - await ContentDialogHelper.CreateInfoDialog( - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString), - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString), - LocaleManager.Instance[LocaleKeys.InputDialogOk], - "", - LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - } - } - else - { - await UserErrorDialog.ShowUserErrorDialog(userError); - Device.Dispose(); - - return false; - } - } - } - } - - Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); - - if (_isFirmwareTitle) - { - Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); - - if (!Device.LoadNca(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - } - else if (Directory.Exists(ApplicationPath)) - { - string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); - - if (romFsFiles.Length == 0) - { - romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); - } - - if (romFsFiles.Length > 0) - { - Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); - - if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) - { - Device.Dispose(); - - return false; - } - } - else - { - Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); - - if (!Device.LoadCart(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - } - } - else if (File.Exists(ApplicationPath)) - { - switch (Path.GetExtension(ApplicationPath).ToLowerInvariant()) - { - case ".xci": - { - Logger.Info?.Print(LogClass.Application, "Loading as XCI."); - - if (!Device.LoadXci(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - - break; - } - case ".nca": - { - Logger.Info?.Print(LogClass.Application, "Loading as NCA."); - - if (!Device.LoadNca(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - - break; - } - case ".nsp": - case ".pfs0": - { - Logger.Info?.Print(LogClass.Application, "Loading as NSP."); - - if (!Device.LoadNsp(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - - break; - } - default: - { - Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); - - try - { - if (!Device.LoadProgram(ApplicationPath)) - { - Device.Dispose(); - - return false; - } - } - catch (ArgumentOutOfRangeException) - { - Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); - - Device.Dispose(); - - return false; - } - - break; - } - } - } - else - { - Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); - - Device.Dispose(); - - return false; - } - - DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); - - ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => - { - appMetadata.UpdatePreGame(); - }); - - return true; - } - - internal void Resume() - { - Device?.System.TogglePauseEmulation(false); - - _viewModel.IsPaused = false; - _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); - Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); - } - - internal void Pause() - { - Device?.System.TogglePauseEmulation(true); - - _viewModel.IsPaused = true; - _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); - Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); - } - - private void InitializeSwitchInstance() - { - // Initialize KeySet. - VirtualFileSystem.ReloadKeySet(); - - // Initialize Renderer. - IRenderer renderer; - - if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) - { - renderer = new VulkanRenderer( - Vk.GetApi(), - (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, - VulkanHelper.GetRequiredInstanceExtensions, - ConfigurationState.Instance.Graphics.PreferredGpu.Value); - } - else - { - renderer = new OpenGLRenderer(); - } - - BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - - var isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); - if (isGALThreaded) - { - renderer = new ThreadedRenderer(renderer); - } - - Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}"); - - // Initialize Configuration. - var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; - - HLEConfiguration configuration = new(VirtualFileSystem, - _viewModel.LibHacHorizonManager, - ContentManager, - _accountManager, - _userChannelPersistence, - renderer, - InitializeAudio(), - memoryConfiguration, - _viewModel.UiHandler, - (SystemLanguage)ConfigurationState.Instance.System.Language.Value, - (RegionCode)ConfigurationState.Instance.System.Region.Value, - ConfigurationState.Instance.Graphics.EnableVsync, - ConfigurationState.Instance.System.EnableDockedMode, - ConfigurationState.Instance.System.EnablePtc, - ConfigurationState.Instance.System.EnableInternetAccess, - ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, - ConfigurationState.Instance.System.FsGlobalAccessLogMode, - ConfigurationState.Instance.System.SystemTimeOffset, - ConfigurationState.Instance.System.TimeZone, - ConfigurationState.Instance.System.MemoryManagerMode, - ConfigurationState.Instance.System.IgnoreMissingServices, - ConfigurationState.Instance.Graphics.AspectRatio, - ConfigurationState.Instance.System.AudioVolume, - ConfigurationState.Instance.System.UseHypervisor, - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode); - - Device = new Switch(configuration); - } - - private static IHardwareDeviceDriver InitializeAudio() - { - var availableBackends = new List<AudioBackend> - { - AudioBackend.SDL2, - AudioBackend.SoundIo, - AudioBackend.OpenAl, - AudioBackend.Dummy, - }; - - AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; - - for (int i = 0; i < availableBackends.Count; i++) - { - if (availableBackends[i] == preferredBackend) - { - availableBackends.RemoveAt(i); - availableBackends.Insert(0, preferredBackend); - break; - } - } - - static IHardwareDeviceDriver InitializeAudioBackend<T>(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new() - { - if (T.IsSupported) - { - return new T(); - } - - Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}."); - - return null; - } - - IHardwareDeviceDriver deviceDriver = null; - - for (int i = 0; i < availableBackends.Count; i++) - { - AudioBackend currentBackend = availableBackends[i]; - AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy; - - deviceDriver = currentBackend switch - { - AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend), - AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend), - AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend), - _ => new DummyHardwareDeviceDriver(), - }; - - if (deviceDriver != null) - { - ConfigurationState.Instance.System.AudioBackend.Value = currentBackend; - break; - } - } - - MainWindowViewModel.SaveConfig(); - - return deviceDriver; - } - - private void Window_BoundsChanged(object sender, Size e) - { - Width = (int)e.Width; - Height = (int)e.Height; - - SetRendererWindowSize(e); - } - - private void MainLoop() - { - while (_isActive) - { - UpdateFrame(); - - // Polling becomes expensive if it's not slept. - Thread.Sleep(1); - } - } - - private void RenderLoop() - { - Dispatcher.UIThread.InvokeAsync(() => - { - if (_viewModel.StartGamesInFullscreen) - { - _viewModel.WindowState = WindowState.FullScreen; - } - - if (_viewModel.WindowState == WindowState.FullScreen) - { - _viewModel.ShowMenuAndStatusBar = false; - } - }); - - _renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer; - - _renderer.ScreenCaptured += Renderer_ScreenCaptured; - - (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); - - Device.Gpu.Renderer.Initialize(_glLogLevel); - - _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); - _renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - _renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - _renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); - - Width = (int)RendererHost.Bounds.Width; - Height = (int)RendererHost.Bounds.Height; - - _renderer.Window.SetSize((int)(Width * _topLevel.RenderScaling), (int)(Height * _topLevel.RenderScaling)); - - _chrono.Start(); - - Device.Gpu.Renderer.RunLoop(() => - { - Device.Gpu.SetGpuThread(); - Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); - - _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); - - while (_isActive) - { - _ticks += _chrono.ElapsedTicks; - - _chrono.Restart(); - - if (Device.WaitFifo()) - { - Device.Statistics.RecordFifoStart(); - Device.ProcessFrame(); - Device.Statistics.RecordFifoEnd(); - } - - while (Device.ConsumeFrameAvailable()) - { - if (!_renderingStarted) - { - _renderingStarted = true; - _viewModel.SwitchToRenderer(false); - } - - Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); - } - - if (_ticks >= _ticksPerFrame) - { - UpdateStatus(); - } - } - - // Make sure all commands in the run loop are fully executed before leaving the loop. - if (Device.Gpu.Renderer is ThreadedRenderer threaded) - { - threaded.FlushThreadedCommands(); - } - - _gpuDoneEvent.Set(); - }); - - (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); - } - - public void UpdateStatus() - { - // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. - string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; - - if (GraphicsConfig.ResScale != 1) - { - dockedMode += $" ({GraphicsConfig.ResScale}x)"; - } - - StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, - LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", - ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", - dockedMode, - ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), - LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", - $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", - $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); - } - - public async Task ShowExitPrompt() - { - bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; - if (!shouldExit) - { - if (_dialogShown) - { - return; - } - - _dialogShown = true; - - shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); - - _dialogShown = false; - } - - if (shouldExit) - { - Stop(); - } - } - - private bool UpdateFrame() - { - if (!_isActive) - { - return false; - } - - NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - - if (_viewModel.IsActive) - { - if (_isCursorInRenderer) - { - if (ConfigurationState.Instance.Hid.EnableMouse) - { - HideCursor(); - } - else - { - switch (ConfigurationState.Instance.HideCursor.Value) - { - case HideCursorMode.Never: - ShowCursor(); - break; - case HideCursorMode.OnIdle: - if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) - { - HideCursor(); - } - else - { - ShowCursor(); - } - break; - case HideCursorMode.Always: - HideCursor(); - break; - } - } - } - else - { - ShowCursor(); - } - - Dispatcher.UIThread.Post(() => - { - if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) - { - Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); - } - }); - - KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); - - if (currentHotkeyState != _prevHotkeyState) - { - switch (currentHotkeyState) - { - case KeyboardHotkeyState.ToggleVSync: - Device.EnableDeviceVsync = !Device.EnableDeviceVsync; - - break; - case KeyboardHotkeyState.Screenshot: - ScreenshotRequested = true; - break; - case KeyboardHotkeyState.ShowUI: - _viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar; - break; - case KeyboardHotkeyState.Pause: - if (_viewModel.IsPaused) - { - Resume(); - } - else - { - Pause(); - } - break; - case KeyboardHotkeyState.ToggleMute: - if (Device.IsAudioMuted()) - { - Device.SetVolume(_viewModel.VolumeBeforeMute); - } - else - { - _viewModel.VolumeBeforeMute = Device.GetVolume(); - Device.SetVolume(0); - } - - _viewModel.Volume = Device.GetVolume(); - break; - case KeyboardHotkeyState.ResScaleUp: - GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; - break; - case KeyboardHotkeyState.ResScaleDown: - GraphicsConfig.ResScale = - (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; - break; - case KeyboardHotkeyState.VolumeUp: - _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); - Device.SetVolume(_newVolume); - - _viewModel.Volume = Device.GetVolume(); - break; - case KeyboardHotkeyState.VolumeDown: - _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); - Device.SetVolume(_newVolume); - - _viewModel.Volume = Device.GetVolume(); - break; - case KeyboardHotkeyState.None: - (_keyboardInterface as AvaloniaKeyboard).Clear(); - break; - } - } - - _prevHotkeyState = currentHotkeyState; - - if (ScreenshotRequested) - { - ScreenshotRequested = false; - _renderer.Screenshot(); - } - } - - // Touchscreen. - bool hasTouch = false; - - if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) - { - hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - } - - if (!hasTouch) - { - Device.Hid.Touchscreen.Update(); - } - - Device.Hid.DebugPad.Update(); - - return true; - } - - private KeyboardHotkeyState GetHotkeyState() - { - KeyboardHotkeyState state = KeyboardHotkeyState.None; - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) - { - state = KeyboardHotkeyState.ToggleVSync; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) - { - state = KeyboardHotkeyState.Screenshot; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) - { - state = KeyboardHotkeyState.ShowUI; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) - { - state = KeyboardHotkeyState.Pause; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) - { - state = KeyboardHotkeyState.ToggleMute; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) - { - state = KeyboardHotkeyState.ResScaleUp; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) - { - state = KeyboardHotkeyState.ResScaleDown; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) - { - state = KeyboardHotkeyState.VolumeUp; - } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) - { - state = KeyboardHotkeyState.VolumeDown; - } - - return state; - } - } -} diff --git a/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf deleted file mode 100644 index 8f05a4bb..00000000 Binary files a/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf and /dev/null differ diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg deleted file mode 100644 index cf78cf12..00000000 --- a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg +++ /dev/null @@ -1,155 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 1000 355.6" style="enable-background:new 0 0 1000 355.6;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#00BBDB;stroke:#000000;} - .st1{fill:#333333;stroke:#000000;stroke-width:0.93;} - .st2{fill:#333333;stroke:#000000;stroke-width:0.96;} - .st3{fill:#333333;stroke:#000000;stroke-width:0.85;} - .st4{fill:#1A1A1A;stroke:#000000;} - .st5{fill:#333333;stroke:#000000;} - .st6{fill:#1A1A1A;stroke:#1A1A1A;} - .st7{fill:#1A1A1A;stroke:#4D4D4D;} - .st8{stroke:#4D4D4D;} - .st9{stroke:#000000;} -</style> -<g id="g344"> - <path id="path66-7" d="M906.6,6.5v7.9c0,3.6-2.9,6.4-6.5,6.5H71.2c-3.6,0-6.4-2.9-6.5-6.5V6.5c0-3.6,2.9-6.4,6.5-6.5L207,0 - c4.9,0,9.6,1.2,14,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,13.9-3.4l269.7,0c4.9,0,9.6,1.2,14,3.4l13,6.7H716l13-6.7 - c4.3-2.2,9.1-3.4,13.9-3.4l157.2,0C903.7,0,906.6,2.9,906.6,6.5L906.6,6.5L906.6,6.5z M65.7,14.4c0,3,2.4,5.5,5.5,5.5h828.9 - c3,0,5.5-2.4,5.5-5.5V6.5c0-3-2.4-5.5-5.5-5.5H742.9c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.2,0.1-0.2,0.1h-79.5 - c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8c-4.2-2.2-8.8-3.3-13.5-3.3H340.1c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.2,0.1-0.2,0.1h-79.5 - c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8C216.3,2.1,211.7,1,207,1L71.2,1c-3,0-5.5,2.4-5.5,5.5L65.7,14.4L65.7,14.4z"/> - <path id="path68-5" d="M858.9,20.3v11.2c0,0.3-0.2,0.5-0.5,0.5H72c-0.3,0-0.5-0.2-0.5-0.5V20.3c0-0.3,0.2-0.5,0.5-0.5h786.4 - C858.7,19.8,858.9,20,858.9,20.3z M857.9,31V20.8H72.5V31H857.9z"/> - <path id="path70-3" d="M1000,37.5v106.2c0,117-94.8,211.9-211.9,211.9l0,0H220.9c-116.8,0-211.8-95.1-211.8-211.9V37.5 - c0-3.6,2.9-6.5,6.5-6.5h978C997.1,31,1000,33.9,1000,37.5L1000,37.5L1000,37.5z M10.1,143.7c0,116.3,94.6,210.9,210.8,210.9h567.2 - C904.4,354.6,999,260,999,143.7V37.5c0-3-2.4-5.5-5.5-5.5h-978c-3,0-5.5,2.4-5.5,5.5v106.2L10.1,143.7L10.1,143.7z"/> - <path id="path98" d="M717.1,6.5v4.2c0,0.6-0.4,1-1,1l0,0h-79.5c-0.6,0-1-0.4-1-1V6.5c0-3.8,3.1-6.9,7-7h67.6 - C714-0.5,717.1,2.6,717.1,6.5z M715.1,9.7V6.5c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2H715.1z"/> - <path id="path100" d="M314.3,6.5v4.2c0,0.6-0.4,1-1,1h-79.5c-0.6,0-1-0.4-1-1V6.5c0-3.8,3.1-6.9,7-7h67.6 - C311.2-0.5,314.3,2.6,314.3,6.5z M312.3,9.7V6.5c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2H312.3z"/> - <path id="path1144" class="st0" d="M997.9,162.4c-3,26.2-8.5,49.9-21,75.1c-10.9,21.9-25.7,41.1-42.5,57.5 - c-33.2,32.3-77.8,53.7-126.7,59c-3.4,0.4-30.5,0.1-72.2,0.3c-158.5,1.1-520.6,0.9-534.4-0.5c-17.7-1.8-36.5-6.1-52.4-12 - c-7.5-2.8-15.8-7.1-23.1-10.7C61.8,299.6,21.2,238.8,12,168.6C9.6,150.4,8.7,35.5,11,33.1c0.1-0.1,2.3-0.9,7-1 - c11.1-0.4,36-0.4,79.4-1.3c32.7-0.7,76.3,0,132.5-0.1c70.3-0.2,160.2,1.6,274.8,1.6c484.5,0,491.2-1.4,492.4,0.9 - c0.2,0.3,1.7,2.5,1.8,5.3c1.1,21.9-0.5,119.2-0.7,120.6L997.9,162.4L997.9,162.4z"/> - <polygon id="polygon80" class="st1" points="470.2,195 470.2,168.3 448.1,181.7 "/> - <polygon id="polygon82" class="st2" points="627.3,181.3 605.6,194.1 605.6,168.5 "/> - <polygon id="polygon84" class="st1" points="538.1,271.6 551.5,249.7 524.7,249.7 "/> - <polygon id="polygon86" class="st3" points="551.6,114.8 524.1,114.8 537.9,89.2 "/> - <path id="path102" d="M139.3,338.3c0,0.3-0.1,0.5-0.3,0.7l-3.4,3.4c-2,2-5.1,2.6-7.8,1.5C50.1,309.1,0.1,231.9,0,146.7l0-51.2 - c0-3.8,3.1-6.9,7-7h2.6c0.6,0,1,0.4,1,1v54.2C10.5,228.2,61,304.5,138.7,337.4c0.3,0.1,0.5,0.4,0.6,0.7L139.3,338.3L139.3,338.3z - M2,146.7c0.1,84.4,49.7,160.9,126.7,195.4c1.9,0.8,4.1,0.4,5.5-1.1l2.4-2.4C58.8,305,8.5,228.3,8.6,143.6V90.4H7c-2.7,0-5,2.2-5,5 - L2,146.7L2,146.7z"/> - <path id="path104" d="M116.1,60v46.9c0,2.2-1.8,4-4,4h-11.7c-2.2,0-4-1.8-4-4V60c0-2.2,1.8-4,4-4h11.7 - C114.3,56,116.1,57.8,116.1,60z M98.5,106.9c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2V60c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2 - V106.9z"/> - <path id="path106" d="M502.9,181.7c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5S502.9,160.4,502.9,181.7 - L502.9,181.7z M428,181.7c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5S428,161.5,428,181.7L428,181.7z"/> - <path id="path108" d="M649.8,181.7c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5S649.8,160.4,649.8,181.7 - L649.8,181.7z M574.9,181.7c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 - C591.2,145.2,574.9,161.5,574.9,181.7L574.9,181.7z"/> - <path id="path110" d="M576.3,108.2c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5l0,0 - C559.1,69.8,576.3,87,576.3,108.2z M501.4,108.2c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 - C517.7,71.8,501.4,88.1,501.4,108.2L501.4,108.2L501.4,108.2z"/> - <path id="path112" d="M576.3,255.1c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5l0,0 - C559.1,216.7,576.3,233.9,576.3,255.1z M501.4,255.1c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 - C517.7,218.7,501.4,235,501.4,255.1L501.4,255.1L501.4,255.1z"/> - <path id="path114" d="M753.7,105.5v45c0,5.5-4.4,9.9-9.9,9.9h-45c-5.5,0-9.9-4.4-9.9-9.9v-45c0-5.5,4.4-9.9,9.9-9.9h45 - C749.3,95.6,753.7,100.1,753.7,105.5z M690.9,150.5c0,4.4,3.6,7.9,7.9,7.9h45c4.4,0,7.9-3.6,7.9-7.9v-45c0-4.4-3.6-7.9-7.9-7.9h-45 - c-4.4,0-7.9,3.6-7.9,7.9V150.5z"/> - <path id="path116" d="M741.7,128c0,11.3-9.2,20.4-20.4,20.4s-20.4-9.2-20.4-20.4s9.2-20.4,20.4-20.4l0,0 - C732.6,107.6,741.7,116.7,741.7,128z M702.8,128c0,10.2,8.3,18.4,18.4,18.4s18.4-8.3,18.4-18.4s-8.3-18.4-18.4-18.4 - S702.9,117.8,702.8,128z"/> - <path id="path118" d="M260.2,244.8v12.3c0,0.6-0.4,1-1,1l0,0c-39.6-1.7-71.3-33.4-73-73c0-0.3,0.1-0.5,0.3-0.7s0.4-0.3,0.7-0.3 - h12.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9C257.6,238.4,260.2,241.3,260.2,244.8L260.2,244.8z M258.2,256v-11.2 - c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-11.2C190.3,223.4,220.8,253.9,258.2,256L258.2,256z - "/> - <path id="path120" d="M338.9,185L338.9,185c-1.7,39.6-33.4,71.3-73,73c-0.6,0-1-0.4-1-1v-12.3c0-3.5,2.6-6.4,6-6.9 - c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6H338C338.5,184,338.9,184.5,338.9,185L338.9,185z M266.9,256 - c37.4-2.2,67.8-32.6,70-70h-11.2c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9V256L266.9,256z"/> - <path id="path122" d="M338.9,178.3c0,0.6-0.4,1-1,1h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9 - c-3.4-0.5-6-3.4-6-6.9v-12.3c0-0.6,0.4-1,1-1l0,0C305.5,107,337.2,138.7,338.9,178.3L338.9,178.3L338.9,178.3z M266.9,118.6 - c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2c-2.2-37.4-32.6-67.8-70-70V118.6z"/> - <path id="path124" d="M260.2,106.3v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-12.3 - c-0.3,0-0.5-0.1-0.7-0.3s-0.3-0.5-0.3-0.7c1.7-39.6,33.4-71.3,73-73C259.7,105.3,260.2,105.7,260.2,106.3L260.2,106.3L260.2,106.3z - M188.2,177.3h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2 - C220.8,109.5,190.3,140,188.2,177.3L188.2,177.3L188.2,177.3z"/> - <path id="path126" d="M339,181.7c0,1.2,0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3 - c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1c-1.1,0.1-2.2,0.1-3.4,0.1s-2.3,0-3.4-0.1 - c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3 - c-0.5,0-1-0.4-1-0.9c-0.1-2.3-0.1-4.5,0-6.8c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6 - c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1c1.1-0.1,2.2-0.1,3.4-0.1s2.3,0,3.4,0.1c0.5,0,0.9,0.5,0.9,1v12.3 - c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3c0.5,0,1,0.4,1,0.9C339,179.4,339,180.5,339,181.7 - L339,181.7z M337,184c0.1-1.5,0.1-3.1,0-4.7h-11.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3 - h-4.6v11.4c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3c-0.1,1.5-0.1,3.1,0,4.7h11.3 - c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9v11.3h4.6v-11.3c0-3.5,2.6-6.4,6-6.9 - c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6L337,184L337,184z"/> - <path id="path179" class="st4" d="M574.3,261.6c-3.2,14.7-12.7,24.9-27.1,29c-3.4,1-6.3,1.2-11.7,0.9c-6.5-0.3-7.8-0.7-13.5-3.5 - c-22.9-11.4-27.9-40.7-10-58.6c19.4-19.4,51.9-12,60.8,13.9C574.5,248.5,575.3,257.1,574.3,261.6L574.3,261.6L574.3,261.6z - M538,249c-7.3,0-13.4,0.2-13.4,0.4c0,0.9,13.2,23.3,13.5,23c0.7-0.7,13.1-22.2,13.2-22.8C551.3,249.2,545.4,249,538,249L538,249 - L538,249z"/> - <path id="path181" class="st4" d="M500.6,187.1c-0.9,6.9-5,15-10.4,20.5c-14.5,14.6-36.9,14.6-51.4-0.1c-22.9-23-7-62.3,25.4-62.3 - c10.4,0,18.8,3.5,26,10.7C498.4,164.3,502.1,175.2,500.6,187.1L500.6,187.1L500.6,187.1z M469.9,168.5 - c-2.4,0.9-22.4,12.8-22.4,13.3c0,0.4,9,5.9,19.9,12l3.6,2v-13.9C471,169.7,470.9,168.1,469.9,168.5L469.9,168.5L469.9,168.5z"/> - <path id="path185" class="st4" d="M647,190.4c-3.5,12.9-13.7,23.1-26.6,26.5c-22.7,5.9-45.2-11.6-45.3-35.2 - c0-7.4,0.9-11.3,4.5-18.1c8.8-16.9,30.4-23.9,47.9-15.4C643,155.8,651.4,173.9,647,190.4L647,190.4L647,190.4z M616.9,174.2 - c-6.3-3.6-11.6-6.5-11.8-6.3s-0.3,6.4-0.1,13.8l0.2,13.5l11.6-6.7c6.4-3.7,11.6-6.9,11.6-7.2C628.4,181.1,623.2,177.8,616.9,174.2 - L616.9,174.2z"/> - <path id="path187" class="st4" d="M574.2,111.7c0,0.3-0.3,1.6-0.5,2.9c-2.2,11.2-9.7,20.9-20.1,26.2c-5.6,2.8-12.2,4.2-17.9,3.8 - c-14.8-1.1-27.7-11.3-32.5-25.6c-2.9-8.8-2.2-18,2.2-26.7s12.1-15.5,21.3-18.6c15.3-5.3,32.2,0.7,41.6,14.7c4,6,6.3,13.4,6.2,20.2 - C574.3,109.9,574.3,111.4,574.2,111.7L574.2,111.7L574.2,111.7z M551.1,112.4c-0.4-0.7-1.6-3-2.8-5.1c-4.8-8.7-9.2-15.6-10-16.2 - c-0.4-0.3-0.5-0.3-0.9,0c-1.3,0.9-13.5,21.5-13.5,22.9c0,0.3,0.1,0.6,0.2,0.7c0.5,0.4,4.8,0.6,13.9,0.6c9.1,0,13-0.2,13.5-0.6 - C551.9,114.3,551.8,113.8,551.1,112.4L551.1,112.4L551.1,112.4z"/> - <path id="path189" class="st5" d="M739.1,131.6c-0.2,1-1,3-1.6,4.4c-1,2.1-1.6,2.9-3.5,4.8c-1.9,1.9-2.7,2.5-4.8,3.5 - c-5.5,2.7-10.6,2.7-16.1,0c-2.1-1-2.8-1.6-4.8-3.5c-3.6-3.6-5.3-7.7-5.3-12.7c0-8.4,5.7-15.7,13.9-17.8c2-0.5,6.2-0.5,8.3,0 - c6.3,1.5,11.5,6.3,13.4,12.7C739.4,125.4,739.6,129.2,739.1,131.6L739.1,131.6L739.1,131.6z"/> - <path id="path191" class="st4" d="M751.3,152.4c-0.4,1.6-1.3,3-2.6,4.1c-2.1,1.8-0.9,1.7-27.4,1.7s-25.3,0.1-27.5-1.9 - c-0.7-0.6-1.5-1.7-1.9-2.5l-0.7-1.5v-48.3l0.9-1.8c0.6-1.3,1.2-2,2.1-2.7c2.3-1.8,1.3-1.7,27.8-1.6c23.8,0.1,23.9,0.1,25.1,0.6 - c1.6,0.7,3.1,2.3,3.9,3.9l0.7,1.3l0,23.8C751.6,145.6,751.5,151.4,751.3,152.4L751.3,152.4L751.3,152.4z M741.6,125.1 - c-1.5-10.6-10.7-18.1-21.4-17.5c-3.4,0.2-5.3,0.7-8.1,2c-9.3,4.6-13.7,15.5-10.2,25.3c1,2.8,2.2,4.7,4.3,7c2.8,3,6.3,5.1,10.3,6 - c2.2,0.5,7.2,0.5,9.4,0c3.9-0.9,7.4-3,10.2-6c2.1-2.2,3.3-4.2,4.3-6.7C741.7,131.9,742.1,128.5,741.6,125.1L741.6,125.1 - L741.6,125.1z"/> - <path id="path1082" class="st6" d="M330.4,183.8c-6,0-6.5,0.1-7.9,0.7c-2.1,1.1-3.4,2.9-3.9,5.6c-2.7,15.1-10.1,27.5-21.9,36.5 - c-6.5,5-15.1,8.8-23.2,10.4c-4.3,0.8-5.7,1.4-7,3c-1.5,1.8-1.8,3.3-1.8,10v5.9h-4.2v-6.3c0-6.1,0-6.3-0.9-8 - c-1.2-2.4-2.7-3.3-7.3-4.2c-23.2-4.6-41-22.4-45.4-45.6c-0.8-4.2-1.6-5.6-3.8-6.9c-1.5-0.9-1.6-0.9-8.1-1l-6.6-0.1v-4.2h6.3 - c6.2,0,6.3,0,8-0.9c2.6-1.3,3.4-2.6,4.4-7.6c3.8-19.4,17-35.1,35.4-42.3c2.7-1,5.9-1.9,12-3.3c2.6-0.6,4.2-1.7,5.2-3.6 - c0.6-1.1,0.7-2,0.8-7.9l0.1-6.6h4.2v6.3c0,6.1,0,6.3,0.9,8c1.1,2.2,2.9,3.4,5.9,3.9c23.8,4.1,42.3,22.2,46.8,46 - c0.8,4.2,1.7,5.7,4.1,7c1.5,0.8,1.9,0.8,8,0.9l6.4,0.1v4.2L330.4,183.8L330.4,183.8z"/> - <path id="path1084" class="st7" d="M257.3,255.8c-0.5-0.1-1.9-0.3-3.2-0.4c-3.9-0.4-10.1-1.7-14.8-3.3c-24.1-8-42.7-28.1-48.9-52.7 - c-0.8-3.1-1.6-7.6-1.9-11.3l-0.2-2h5.9c8.5,0,9.1,0.4,10.4,6.5c3.7,18.4,15.2,33.7,31.9,41.9c5.1,2.6,10.1,4.1,17,5.5 - c2.4,0.5,4,1.8,4.4,3.7c0.2,0.7,0.3,3.8,0.3,6.8v5.5L257.3,255.8L257.3,255.8z"/> - <path id="path1086" class="st7" d="M336.4,189.8c-3,27-21.4,50.8-46.9,60.9c-6,2.4-13.4,4.2-18.7,4.7c-1.3,0.1-2.7,0.3-3.1,0.4 - l-0.8,0.2l0.1-6.3c0.1-5.8,0.2-6.4,0.8-7.2c1.2-1.6,2.2-2,6.2-2.9c5.8-1.2,9.4-2.4,14.8-5.2c5.7-2.9,9.8-5.7,14.2-9.9 - c9.1-8.6,15.2-19.7,17.5-31.7c0.7-3.6,1.2-4.6,2.7-5.8c0.8-0.6,1.4-0.7,7.2-0.8c5.9-0.1,6.3-0.1,6.3,0.5 - C336.7,187,336.6,188.4,336.4,189.8L336.4,189.8L336.4,189.8z"/> - <path id="path1088" class="st7" d="M257.7,119.9c-0.9,2.4-1.6,2.8-6.5,3.8c-18.2,3.7-33.5,15.4-41.6,32c-2.4,4.8-3.7,8.6-4.7,13.5 - c-1,4.7-1.6,6.2-2.9,7.1c-1.1,0.7-1.4,0.7-7.4,0.7c-3.5,0-6.3-0.1-6.3-0.3s0.2-1.9,0.5-4c1.5-12,5.6-22.9,12.4-32.8 - c3-4.4,5.6-7.4,9.9-11.6c9.6-9.3,20.7-15.5,33.5-18.8c3.6-0.9,10.9-2.1,12.9-2.1c0.6,0,0.6,0.3,0.6,5.6 - C258.1,116.9,258,119.1,257.7,119.9L257.7,119.9L257.7,119.9z"/> - <path id="path1090" class="st7" d="M330.5,177.3c-5.8-0.1-6.4-0.2-7.2-0.8c-1.6-1.2-2-2.2-2.9-6.3c-4.7-23.5-23.3-41.9-47-46.4 - c-3.5-0.7-4.5-1.1-5.6-2.7c-0.6-0.8-0.7-1.4-0.8-7.2l-0.1-6.3l1.7,0.2c10.5,1.2,18,3.3,26.4,7.5c22.6,11.2,38.1,32.9,41.3,58 - c0.3,2.1,0.5,3.9,0.5,4S333.9,177.3,330.5,177.3L330.5,177.3L330.5,177.3z"/> - <path id="path1092" class="st4" d="M113.5,108c-0.6,0.5-1.8,0.7-7.2,0.7c-4.5,0-6.7-0.2-7-0.5c-0.4-0.4-0.5-6.4-0.5-24.6 - c0-21.3,0.1-24.2,0.7-24.8c0.6-0.5,1.8-0.7,7-0.7c6.1,0,6.4,0,7,1c0.6,0.8,0.7,3.9,0.7,24.6S114,107.4,113.5,108z"/> - <path id="path1094" class="st8" d="M134.2,340.6c-2.8,2.4-3.7,2.2-12.6-2.3c-21.7-10.8-39.6-23.6-56.5-40.5 - c-31.4-31.4-52.2-71.6-59.8-115.3c-2.6-15.1-2.7-16.5-2.9-54L2.3,93.7l1.2-1.4c0.9-1,1.7-1.4,3-1.6l1.8-0.2l0.2,33.8 - c0.2,36.2,0.3,38.8,2.7,53.3c6.2,38.4,22,72.9,47.3,103.3c5.7,6.8,18.3,19.5,25.2,25.2c10,8.4,21.7,16.5,32.4,22.5 - c5.4,3.1,18.7,9.6,19.5,9.6C136.4,338.3,136,339.1,134.2,340.6L134.2,340.6L134.2,340.6z"/> - <path id="path1150" class="st9" d="M616,21.1c-216,0-634,0-526-0.1c108-0.1,614.3-0.3,722.4-0.1c29.2,0,43.2,0.1,45,0.1 - C862.4,21.1,773.6,21.1,616,21.1L616,21.1L616,21.1z"/> - <path id="path1152" class="st4" d="M903.2,19c-2.1,1.5-44.2,0.8-417.6,0.8S70.3,20.6,68.2,19.1c-2-1.4-2.2-3.4-2.2-8.5 - c0-1.8-0.1-3.8,0.2-5.4c0.3-1.4,2-3.2,2.4-3.5c0.5-0.5,4.3-0.3,16.4-0.4c11.3-0.1,29,0.2,55.3,0.2c44.7,0,61.8-0.6,69.8-0.1 - c4.4,0.3,6.1,1.2,8.1,1.9c3.1,1.2,8.2,3.9,10.9,5.6l5,3.2l40.4-0.4c37.6-0.4,39,0.2,41.7-1.6c1.5-1.1,6.1-3,9.3-4.6l5.8-3l7.9-1.3 - l19.8,0.1l15.8,0.1l97.9,0.2c93.1,0.2,126.7-1.2,141.1,0.3c4.2,0.4,6.5,1.6,8.5,2.3c2.7,0.9,4.9,2.1,7.8,3.9l6.1,3.7l15.6-0.4 - l25.1-0.1c26.5-0.1,40.4-0.6,40.9-1.3c0.4-0.6,4-2.7,8.5-4.3l9-3.3l9-1.1l15.9,0.2l57.9,0.2c61.9,0.2,84.5-0.4,85.5,0.5 - c0.9,0.8,2.4,4.5,2.4,8.4C905.8,15.4,905.1,17.6,903.2,19L903.2,19L903.2,19z"/> - <path id="path1154" class="st4" d="M464.9,30.8l-5.2,0l-387-0.4v-8.5h392.2l393-1.1l0.2,5.3l-0.2,4.9L464.9,30.8L464.9,30.8z"/> - <path id="path1156" class="st0" d="M273.3,9.7h-38.6v-3c0-1.7,0.9-3.6,2.1-4.3c2.8-1.7,70.3-1.7,73.1,0c1.2,0.7,2.1,2.6,2.1,4.3v3 - H273.3L273.3,9.7z"/> - <path id="path1158" class="st0" d="M676.2,9.7h-38.7v-3c0-1.8,0.9-3.6,2.1-4.3c2.8-1.7,70.3-1.7,73.2,0c1.2,0.7,2.1,2.6,2.1,4.3v3 - H676.2L676.2,9.7z"/> -</g> -</svg> diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg deleted file mode 100644 index 8097762d..00000000 --- a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg +++ /dev/null @@ -1,341 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 1000 1000.2" style="enable-background:new 0 0 1000 1000.2;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#00BBDB;stroke:#000000;} - .st1{fill:#333333;stroke:#000000;stroke-width:0.93;} - .st2{fill:#333333;stroke:#000000;stroke-width:0.96;} - .st3{fill:#333333;stroke:#000000;stroke-width:0.85;} - .st4{fill:#1A1A1A;stroke:#000000;} - .st5{fill:#333333;stroke:#000000;} - .st6{fill:#1A1A1A;stroke:#1A1A1A;} - .st7{fill:#1A1A1A;stroke:#4D4D4D;} - .st8{stroke:#4D4D4D;} - .st9{stroke:#000000;} - .st10{opacity:0.1;} - .st11{fill:#FF5F55;} - .st12{fill:#FF5F53;} - .st13{fill:#FFFFFF;} - .st14{fill:#999595;stroke:#000000;stroke-width:2.39;stroke-linecap:round;stroke-linejoin:round;} - .st15{fill:#3A3D40;stroke:#000000;stroke-width:2.73;stroke-linecap:round;stroke-linejoin:round;} -</style> -<g id="layer1"> - <g id="g344"> - <path id="path66-7" d="M349.1,906.6h-7.9c-3.6,0-6.4-2.9-6.5-6.5V71.2c0-3.6,2.9-6.4,6.5-6.5h7.9c3.6,0,6.4,2.9,6.5,6.5V207 - c0,4.9-1.2,9.6-3.4,14l-6.7,13v79.2l6.7,13c2.2,4.3,3.4,9.1,3.4,13.9v269.7c0,4.9-1.2,9.6-3.4,14l-6.7,13V716l6.7,13 - c2.2,4.3,3.4,9.1,3.4,13.9v157.2C355.6,903.7,352.7,906.6,349.1,906.6L349.1,906.6L349.1,906.6z M341.2,65.7c-3,0-5.5,2.4-5.5,5.5 - v828.9c0,3,2.4,5.5,5.5,5.5h7.9c3,0,5.5-2.4,5.5-5.5V742.9c0-4.7-1.1-9.3-3.3-13.5l-6.8-13.1c0-0.1-0.1-0.2-0.1-0.2v-79.5 - c0-0.1,0-0.2,0.1-0.2l6.8-13.1c2.2-4.2,3.3-8.8,3.3-13.5V340.1c0-4.7-1.1-9.3-3.3-13.5l-6.8-13.1c0-0.1-0.1-0.2-0.1-0.2v-79.5 - c0-0.1,0-0.2,0.1-0.2l6.8-13.1c2.2-4.2,3.3-8.8,3.3-13.5V71.2c0-3-2.4-5.5-5.5-5.5L341.2,65.7L341.2,65.7z"/> - <path id="path68-5" d="M335.3,858.9h-11.2c-0.3,0-0.5-0.2-0.5-0.5V72c0-0.3,0.2-0.5,0.5-0.5h11.2c0.3,0,0.5,0.2,0.5,0.5v786.4 - C335.8,858.7,335.6,858.9,335.3,858.9z M324.6,857.9h10.2V72.5h-10.2V857.9z"/> - <path id="path70-3" d="M318.1,1000H211.9C94.9,1000,0,905.2,0,788.1l0,0V220.9C0,104.1,95.1,9.1,211.9,9.1h106.2 - c3.6,0,6.5,2.9,6.5,6.5v978C324.6,997.1,321.7,1000,318.1,1000L318.1,1000L318.1,1000z M211.9,10.1C95.6,10.1,1,104.7,1,220.9 - v567.2C1,904.4,95.6,999,211.9,999h106.2c3,0,5.5-2.4,5.5-5.5v-978c0-3-2.4-5.5-5.5-5.5H211.9L211.9,10.1L211.9,10.1z"/> - <path id="path98" d="M349.1,717.1h-4.2c-0.6,0-1-0.4-1-1l0,0v-79.5c0-0.6,0.4-1,1-1h4.2c3.8,0,6.9,3.1,7,7v67.6 - C356.1,714,353,717.1,349.1,717.1z M345.9,715.1h3.2c2.7,0,5-2.2,5-5v-67.6c0-2.7-2.2-5-5-5h-3.2V715.1z"/> - <path id="path100" d="M349.1,314.3h-4.2c-0.6,0-1-0.4-1-1v-79.5c0-0.6,0.4-1,1-1h4.2c3.8,0,6.9,3.1,7,7v67.6 - C356.1,311.2,353,314.3,349.1,314.3z M345.9,312.3h3.2c2.7,0,5-2.2,5-5v-67.6c0-2.7-2.2-5-5-5h-3.2V312.3z"/> - <path id="path1144" class="st0" d="M193.2,997.9c-26.2-3-49.9-8.5-75.1-21C96.2,966,77,951.2,60.7,934.4 - C28.4,901.1,7,856.6,1.7,807.7c-0.4-3.4-0.1-30.5-0.3-72.2C0.3,576.9,0.4,214.9,1.8,201c1.8-17.7,6.1-36.5,12-52.4 - c2.8-7.5,7.1-15.8,10.7-23.1C55.9,61.8,116.8,21.2,187,12c18.2-2.4,133.1-3.3,135.5-0.9c0.1,0.1,0.9,2.3,1,7 - c0.4,11.1,0.4,36,1.3,79.4c0.7,32.7,0,76.3,0.1,132.5c0.2,70.3-1.6,160.2-1.6,274.8c0,484.5,1.4,491.2-0.9,492.4 - c-0.3,0.2-2.5,1.7-5.3,1.8c-21.9,1.1-119.2-0.5-120.6-0.7L193.2,997.9L193.2,997.9z"/> - <polygon id="polygon80" class="st1" points="173.9,448.1 160.6,470.2 187.3,470.2 "/> - <polygon id="polygon82" class="st2" points="187.1,605.6 174.3,627.3 161.5,605.6 "/> - <polygon id="polygon84" class="st1" points="105.9,524.7 84,538.1 105.9,551.5 "/> - <polygon id="polygon86" class="st3" points="266.4,537.9 240.8,551.6 240.8,524.1 "/> - <path id="path102" d="M17.3,139.3c-0.3,0-0.5-0.1-0.7-0.3l-3.4-3.4c-2-2-2.6-5.1-1.5-7.8C46.5,50.1,123.7,0.1,208.9,0h51.2 - c3.8,0,6.9,3.1,7,7v2.6c0,0.6-0.4,1-1,1h-54.2C127.4,10.5,51.1,61,18.2,138.7c-0.1,0.3-0.4,0.5-0.7,0.6L17.3,139.3L17.3,139.3z - M208.9,2C124.5,2.1,48,51.7,13.5,128.7c-0.8,1.9-0.4,4.1,1.1,5.5l2.4,2.4C50.6,58.8,127.3,8.5,212,8.6h53.2V7c0-2.7-2.2-5-5-5 - L208.9,2L208.9,2z"/> - <path id="path104" d="M295.6,116.1h-46.9c-2.2,0-4-1.8-4-4v-11.7c0-2.2,1.8-4,4-4h46.9c2.2,0,4,1.8,4,4v11.7 - C299.6,114.3,297.8,116.1,295.6,116.1z M248.7,98.5c-1.1,0-2,0.9-2,2v11.7c0,1.1,0.9,2,2,2h46.9c1.1,0,2-0.9,2-2v-11.7 - c0-1.1-0.9-2-2-2H248.7z"/> - <path id="path106" d="M173.9,502.9c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5S195.2,502.9,173.9,502.9 - L173.9,502.9z M173.9,428c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5S194.1,428,173.9,428L173.9,428z" - /> - <path id="path108" d="M173.9,649.8c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5S195.2,649.8,173.9,649.8 - L173.9,649.8z M173.9,574.9c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 - C210.4,591.2,194.1,574.9,173.9,574.9L173.9,574.9z"/> - <path id="path110" d="M247.4,576.3c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5l0,0 - C285.8,559.1,268.6,576.3,247.4,576.3z M247.4,501.4c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 - C283.8,517.7,267.5,501.4,247.4,501.4L247.4,501.4L247.4,501.4z"/> - <path id="path112" d="M100.5,576.3c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5l0,0 - C138.9,559.1,121.7,576.3,100.5,576.3z M100.5,501.4c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 - C136.9,517.7,120.6,501.4,100.5,501.4L100.5,501.4L100.5,501.4z"/> - <path id="path114" d="M250.1,753.7h-45c-5.5,0-9.9-4.4-9.9-9.9v-45c0-5.5,4.4-9.9,9.9-9.9h45c5.5,0,9.9,4.4,9.9,9.9v45 - C260,749.3,255.5,753.7,250.1,753.7z M205.1,690.9c-4.4,0-7.9,3.6-7.9,7.9v45c0,4.4,3.6,7.9,7.9,7.9h45c4.4,0,7.9-3.6,7.9-7.9v-45 - c0-4.4-3.6-7.9-7.9-7.9H205.1z"/> - <path id="path116" d="M227.6,741.7c-11.3,0-20.4-9.2-20.4-20.4s9.2-20.4,20.4-20.4s20.4,9.2,20.4,20.4l0,0 - C248,732.6,238.9,741.7,227.6,741.7z M227.6,702.8c-10.2,0-18.4,8.3-18.4,18.4s8.3,18.4,18.4,18.4s18.4-8.3,18.4-18.4 - S237.8,702.9,227.6,702.8z"/> - <path id="path118" d="M110.8,260.2H98.5c-0.6,0-1-0.4-1-1l0,0c1.7-39.6,33.4-71.3,73-73c0.3,0,0.5,0.1,0.7,0.3s0.3,0.4,0.3,0.7 - v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9C117.2,257.6,114.3,260.2,110.8,260.2L110.8,260.2z M99.6,258.2h11.2 - c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2C132.2,190.3,101.7,220.8,99.6,258.2L99.6,258.2 - z"/> - <path id="path120" d="M170.6,338.9L170.6,338.9c-39.6-1.7-71.3-33.4-73-73c0-0.6,0.4-1,1-1h12.3c3.5,0,6.4,2.6,6.9,6 - c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9V338C171.6,338.5,171.1,338.9,170.6,338.9L170.6,338.9z M99.6,266.9 - c2.2,37.4,32.6,67.8,70,70v-11.2c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3H99.6L99.6,266.9z" - /> - <path id="path122" d="M177.3,338.9c-0.6,0-1-0.4-1-1v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6 - h12.3c0.6,0,1,0.4,1,1l0,0C248.6,305.5,216.9,337.2,177.3,338.9L177.3,338.9L177.3,338.9z M237,266.9c-2.5,0-4.6,1.8-4.9,4.3 - c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v11.2c37.4-2.2,67.8-32.6,70-70H237z"/> - <path id="path124" d="M249.3,260.2H237c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-12.3 - c0-0.3,0.1-0.5,0.3-0.7s0.5-0.3,0.7-0.3c39.6,1.7,71.3,33.4,73,73C250.3,259.7,249.9,260.2,249.3,260.2L249.3,260.2L249.3,260.2z - M178.3,188.2v11.2c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2 - C246.1,220.8,215.6,190.3,178.3,188.2L178.3,188.2L178.3,188.2z"/> - <path id="path126" d="M173.9,339c-1.2,0-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 - c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3H98.5c-0.5,0-1-0.4-1-0.9c-0.1-1.1-0.1-2.2-0.1-3.4s0-2.3,0.1-3.4 - c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1 - c2.3-0.1,4.5-0.1,6.8,0c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3 - c0.5,0,1,0.4,1,0.9c0.1,1.1,0.1,2.2,0.1,3.4s0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9H237c-2.5,0-4.6,1.8-4.9,4.3 - c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1C176.2,339,175.1,339,173.9,339L173.9,339z - M171.6,337c1.5,0.1,3.1,0.1,4.7,0v-11.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h11.3v-4.6H237 - c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3c-1.5-0.1-3.1-0.1-4.7,0v11.3 - c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6H99.4v4.6h11.3c3.5,0,6.4,2.6,6.9,6 - c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9L171.6,337L171.6,337z"/> - <path id="path179" class="st4" d="M94,574.3c-14.7-3.2-24.9-12.7-29-27.1c-1-3.4-1.2-6.3-0.9-11.7c0.3-6.5,0.7-7.8,3.5-13.5 - c11.4-22.9,40.7-27.9,58.6-10c19.4,19.4,12,51.9-13.9,60.8C107.1,574.5,98.5,575.3,94,574.3L94,574.3L94,574.3z M106.6,538 - c0-7.3-0.2-13.4-0.4-13.4c-0.9,0-23.3,13.2-23,13.5c0.7,0.7,22.2,13.1,22.8,13.2C106.4,551.3,106.6,545.4,106.6,538L106.6,538 - L106.6,538z"/> - <path id="path181" class="st4" d="M168.5,500.6c-6.9-0.9-15-5-20.5-10.4c-14.6-14.5-14.6-36.9,0.1-51.4c23-22.9,62.3-7,62.3,25.4 - c0,10.4-3.5,18.8-10.7,26C191.3,498.4,180.4,502.1,168.5,500.6L168.5,500.6L168.5,500.6z M187.1,469.9 - c-0.9-2.4-12.8-22.4-13.3-22.4c-0.4,0-5.9,9-12,19.9l-2,3.6h13.9C185.9,471,187.5,470.9,187.1,469.9L187.1,469.9L187.1,469.9z"/> - <path id="path185" class="st4" d="M165.2,647c-12.9-3.5-23.1-13.7-26.5-26.6c-5.9-22.7,11.6-45.2,35.2-45.3 - c7.4,0,11.3,0.9,18.1,4.5c16.9,8.8,23.9,30.4,15.4,47.9C199.8,643,181.7,651.4,165.2,647L165.2,647L165.2,647z M181.4,616.9 - c3.6-6.3,6.5-11.6,6.3-11.8s-6.4-0.3-13.8-0.1l-13.5,0.2l6.7,11.6c3.7,6.4,6.9,11.6,7.2,11.6C174.5,628.4,177.8,623.2,181.4,616.9 - L181.4,616.9z"/> - <path id="path187" class="st4" d="M243.9,574.2c-0.3,0-1.6-0.3-2.9-0.5c-11.2-2.2-20.9-9.7-26.2-20.1c-2.8-5.6-4.2-12.2-3.8-17.9 - c1.1-14.8,11.3-27.7,25.6-32.5c8.8-2.9,18-2.2,26.7,2.2s15.5,12.1,18.6,21.3c5.3,15.3-0.7,32.2-14.7,41.6c-6,4-13.4,6.3-20.2,6.2 - C245.7,574.3,244.2,574.3,243.9,574.2L243.9,574.2L243.9,574.2z M243.2,551.1c0.7-0.4,3-1.6,5.1-2.8c8.7-4.8,15.6-9.2,16.2-10 - c0.3-0.4,0.3-0.5,0-0.9c-0.9-1.3-21.5-13.5-22.9-13.5c-0.3,0-0.6,0.1-0.7,0.2c-0.4,0.5-0.6,4.8-0.6,13.9c0,9.1,0.2,13,0.6,13.5 - C241.3,551.9,241.8,551.8,243.2,551.1L243.2,551.1L243.2,551.1z"/> - <path id="path189" class="st5" d="M224,739.1c-1-0.2-3-1-4.4-1.6c-2.1-1-2.9-1.6-4.8-3.5c-1.9-1.9-2.5-2.7-3.5-4.8 - c-2.7-5.5-2.7-10.6,0-16.1c1-2.1,1.6-2.8,3.5-4.8c3.6-3.6,7.7-5.3,12.7-5.3c8.4,0,15.7,5.7,17.8,13.9c0.5,2,0.5,6.2,0,8.3 - c-1.5,6.3-6.3,11.5-12.7,13.4C230.2,739.4,226.4,739.6,224,739.1L224,739.1L224,739.1z"/> - <path id="path191" class="st4" d="M203.2,751.3c-1.6-0.4-3-1.3-4.1-2.6c-1.8-2.1-1.7-0.9-1.7-27.4s-0.1-25.3,1.9-27.5 - c0.6-0.7,1.7-1.5,2.5-1.9l1.5-0.7h48.3l1.8,0.9c1.3,0.6,2,1.2,2.7,2.1c1.8,2.3,1.7,1.3,1.6,27.8c-0.1,23.8-0.1,23.9-0.6,25.1 - c-0.7,1.6-2.3,3.1-3.9,3.9l-1.3,0.7l-23.8,0C210,751.6,204.2,751.5,203.2,751.3L203.2,751.3L203.2,751.3z M230.5,741.6 - c10.6-1.5,18.1-10.7,17.5-21.4c-0.2-3.4-0.7-5.3-2-8.1c-4.6-9.3-15.5-13.7-25.3-10.2c-2.8,1-4.7,2.2-7,4.3c-3,2.8-5.1,6.3-6,10.3 - c-0.5,2.2-0.5,7.2,0,9.4c0.9,3.9,3,7.4,6,10.2c2.2,2.1,4.2,3.3,6.7,4.3C223.7,741.7,227.1,742.1,230.5,741.6L230.5,741.6 - L230.5,741.6z"/> - <path id="path1082" class="st6" d="M171.8,330.4c0-6-0.1-6.5-0.7-7.9c-1.1-2.1-2.9-3.4-5.6-3.9c-15.1-2.7-27.5-10.1-36.5-21.9 - c-5-6.5-8.8-15.1-10.4-23.2c-0.8-4.3-1.4-5.7-3-7c-1.8-1.5-3.3-1.8-10-1.8h-5.9v-4.2h6.3c6.1,0,6.3,0,8-0.9 - c2.4-1.2,3.3-2.7,4.2-7.3c4.6-23.2,22.4-41,45.6-45.4c4.2-0.8,5.6-1.6,6.9-3.8c0.9-1.5,0.9-1.6,1-8.1l0.1-6.6h4.2v6.3 - c0,6.2,0,6.3,0.9,8c1.3,2.6,2.6,3.4,7.6,4.4c19.4,3.8,35.1,17,42.3,35.4c1,2.7,1.9,5.9,3.3,12c0.6,2.6,1.7,4.2,3.6,5.2 - c1.1,0.6,2,0.7,7.9,0.8l6.6,0.1v4.2h-6.3c-6.1,0-6.3,0-8,0.9c-2.2,1.1-3.4,2.9-3.9,5.9c-4.1,23.8-22.2,42.3-46,46.8 - c-4.2,0.8-5.7,1.7-7,4.1c-0.8,1.5-0.8,1.9-0.9,8l-0.1,6.4h-4.2L171.8,330.4L171.8,330.4z"/> - <path id="path1084" class="st7" d="M99.8,257.3c0.1-0.5,0.3-1.9,0.4-3.2c0.4-3.9,1.7-10.1,3.3-14.8c8-24.1,28.1-42.7,52.7-48.9 - c3.1-0.8,7.6-1.6,11.3-1.9l2-0.2v5.9c0,8.5-0.4,9.1-6.5,10.4c-18.4,3.7-33.7,15.2-41.9,31.9c-2.6,5.1-4.1,10.1-5.5,17 - c-0.5,2.4-1.8,4-3.7,4.4c-0.7,0.2-3.8,0.3-6.8,0.3h-5.5L99.8,257.3L99.8,257.3z"/> - <path id="path1086" class="st7" d="M165.8,336.4c-27-3-50.8-21.4-60.9-46.9c-2.4-6-4.2-13.4-4.7-18.7c-0.1-1.3-0.3-2.7-0.4-3.1 - l-0.2-0.8l6.3,0.1c5.8,0.1,6.4,0.2,7.2,0.8c1.6,1.2,2,2.2,2.9,6.2c1.2,5.8,2.4,9.4,5.2,14.8c2.9,5.7,5.7,9.8,9.9,14.2 - c8.6,9.1,19.7,15.2,31.7,17.5c3.6,0.7,4.6,1.2,5.8,2.7c0.6,0.8,0.7,1.4,0.8,7.2c0.1,5.9,0.1,6.3-0.5,6.3 - C168.6,336.7,167.2,336.6,165.8,336.4L165.8,336.4L165.8,336.4z"/> - <path id="path1088" class="st7" d="M235.7,257.7c-2.4-0.9-2.8-1.6-3.8-6.5c-3.7-18.2-15.4-33.5-32-41.6c-4.8-2.4-8.6-3.7-13.5-4.7 - c-4.7-1-6.2-1.6-7.1-2.9c-0.7-1.1-0.7-1.4-0.7-7.4c0-3.5,0.1-6.3,0.3-6.3s1.9,0.2,4,0.5c12,1.5,22.9,5.6,32.8,12.4 - c4.4,3,7.4,5.6,11.6,9.9c9.3,9.6,15.5,20.7,18.8,33.5c0.9,3.6,2.1,10.9,2.1,12.9c0,0.6-0.3,0.6-5.6,0.6 - C238.7,258.1,236.5,258,235.7,257.7L235.7,257.7L235.7,257.7z"/> - <path id="path1090" class="st7" d="M178.3,330.5c0.1-5.8,0.2-6.4,0.8-7.2c1.2-1.6,2.2-2,6.3-2.9c23.5-4.7,41.9-23.3,46.4-47 - c0.7-3.5,1.1-4.5,2.7-5.6c0.8-0.6,1.4-0.7,7.2-0.8l6.3-0.1l-0.2,1.7c-1.2,10.5-3.3,18-7.5,26.4c-11.2,22.6-32.9,38.1-58,41.3 - c-2.1,0.3-3.9,0.5-4,0.5S178.3,333.9,178.3,330.5L178.3,330.5L178.3,330.5z"/> - <path id="path1092" class="st4" d="M247.6,113.5c-0.5-0.6-0.7-1.8-0.7-7.2c0-4.5,0.2-6.7,0.5-7c0.4-0.4,6.4-0.5,24.6-0.5 - c21.3,0,24.2,0.1,24.8,0.7c0.5,0.6,0.7,1.8,0.7,7c0,6.1,0,6.4-1,7c-0.8,0.6-3.9,0.7-24.6,0.7S248.2,114,247.6,113.5z"/> - <path id="path1094" class="st8" d="M15,134.2c-2.3-2.8-2.2-3.7,2.3-12.6C28,99.9,40.9,82,57.8,65.1C89.1,33.7,129.4,12.8,173,5.3 - c15.1-2.6,16.5-2.7,54-2.9l34.8-0.2l1.4,1.2c1,0.9,1.4,1.7,1.6,3l0.2,1.8l-33.8,0.2C195,8.6,192.5,8.8,178,11.1 - c-38.4,6.2-72.9,22-103.3,47.3c-6.8,5.7-19.5,18.3-25.2,25.2c-8.4,10-16.5,21.7-22.5,32.4c-3.1,5.4-9.6,18.7-9.6,19.5 - C17.3,136.4,16.5,136,15,134.2L15,134.2L15,134.2z"/> - <path id="path1150" class="st9" d="M334.4,616c0-216,0-634,0.1-526c0.1,108,0.3,614.3,0.1,722.4c0,29.2-0.1,43.2-0.1,45 - C334.5,862.4,334.5,773.6,334.4,616L334.4,616L334.4,616z"/> - <path id="path1152" class="st4" d="M336.6,903.2c-1.5-2.1-0.8-44.2-0.8-417.6S335,70.3,336.5,68.2c1.4-2,3.4-2.2,8.5-2.2 - c1.8,0,3.8-0.1,5.4,0.2c1.4,0.3,3.2,2,3.5,2.4c0.5,0.5,0.3,4.3,0.4,16.4c0.1,11.3-0.2,29-0.2,55.3c0,44.7,0.6,61.8,0.1,69.8 - c-0.3,4.4-1.2,6.1-1.9,8.1c-1.2,3.1-3.9,8.2-5.6,10.9l-3.2,5l0.4,40.4c0.4,37.6-0.2,39,1.6,41.7c1.1,1.5,3,6.1,4.6,9.3l3,5.8 - l1.3,7.9l-0.1,19.8l-0.1,15.8l-0.2,97.9c-0.2,93.1,1.2,126.7-0.3,141.1c-0.4,4.2-1.6,6.5-2.3,8.5c-0.9,2.7-2.1,4.9-3.9,7.8 - l-3.7,6.1l0.4,15.6l0.1,25.1c0.1,26.5,0.6,40.4,1.3,40.9c0.6,0.4,2.7,4,4.3,8.5l3.3,9l1.1,9l-0.2,15.9l-0.2,57.9 - c-0.2,61.9,0.4,84.5-0.5,85.5c-0.8,0.9-4.5,2.4-8.4,2.4C340.2,905.8,338,905.1,336.6,903.2L336.6,903.2L336.6,903.2z"/> - <path id="path1154" class="st4" d="M325.2,464.9V72.7h8.5v392.2l1.1,393l-5.3,0.2l-4.9-0.2L325.2,464.9L325.2,464.9z"/> - <path id="path1156" class="st0" d="M345.9,273.3v-38.6h3c1.7,0,3.6,0.9,4.3,2.1c1.7,2.8,1.7,70.3,0,73.1c-0.7,1.2-2.6,2.1-4.3,2.1 - h-3V273.3L345.9,273.3z"/> - <path id="path1158" class="st0" d="M345.9,676.2v-38.7h3c1.8,0,3.6,0.9,4.3,2.1c1.7,2.8,1.7,70.3,0,73.2c-0.7,1.2-2.6,2.1-4.3,2.1 - h-3V676.2L345.9,676.2z"/> - </g> - <g id="g315"> - <g id="g64" class="st10"> - <path id="path36" class="st11" d="M654.6,233.9v79.5h-4.2c-3.3,0-6-2.7-6-6v-67.6c0-3.3,2.7-6,6-6h4.2V233.9z"/> - <path id="path38" class="st11" d="M654.6,636.6v79.5h-4.2c-3.3,0-6-2.7-6-6v-67.6c0-3.3,2.7-6,6-6L654.6,636.6L654.6,636.6z"/> - <path id="path40" class="st11" d="M985.7,134.9l-3.4,3.4C949.1,60.3,872.5,9.6,787.6,9.6h-54.2V7c0-3.3,2.7-6,6-6h51.2 - C875.4,1,952.3,50.8,987,128.2C988,130.5,987.5,133.1,985.7,134.9L985.7,134.9L985.7,134.9z"/> - <path id="path42" class="st11" d="M736.2,94.5V82.8c0-1.6-1.3-3-3-3h-11.7c-1.6,0-3,1.3-3,3l0,0v11.7c0,1.6-1.3,3-3,3h-11.7 - c-1.6,0-3,1.3-3,3l0,0v11.7c0,1.6,1.3,3,3,3h11.7c1.6,0,3,1.3,3,3l0,0v11.7c0,1.6,1.3,3,3,3h11.7c1.6,0,3-1.3,3-3l0,0v-11.7 - c0-1.6,1.3-3,3-3h11.7c1.6,0,3-1.3,3-3l0,0v-11.7c0-1.6-1.3-3-3-3h-11.7C737.5,97.5,736.2,96.1,736.2,94.5L736.2,94.5z"/> - <circle id="circle44" class="st11" cx="825.6" cy="333.9" r="37.5"/> - <circle id="circle46" class="st11" cx="825.6" cy="187.1" r="37.5"/> - <circle id="circle48" class="st11" cx="899" cy="260.5" r="37.5"/> - <circle id="circle50" class="st11" cx="752.2" cy="260.5" r="37.5"/> - <circle id="circle52" class="st11" cx="771.7" cy="721.3" r="27.9"/> - <path id="path54" class="st11" d="M822.3,460.3v12.3c0,3-2.2,5.5-5.2,5.9c-25.2,3.7-45,23.5-48.7,48.7c-0.4,2.9-2.9,5.1-5.9,5.2 - h-12.3C751.9,493.3,783.2,462,822.3,460.3L822.3,460.3L822.3,460.3z"/> - <path id="path56" class="st11" d="M822.3,598.8v12.3c-39.1-1.7-70.3-33-72.1-72h12.3c3,0,5.5,2.2,5.9,5.2 - c3.7,25.2,23.5,45,48.7,48.7C820.1,593.3,822.3,595.8,822.3,598.8L822.3,598.8L822.3,598.8z"/> - <path id="path58" class="st11" d="M901,539c-1.7,39.1-33,70.3-72.1,72v-12.3c0-3,2.2-5.5,5.2-5.9c25.2-3.7,45-23.5,48.7-48.7 - c0.4-2.9,2.9-5.1,5.9-5.2L901,539L901,539z"/> - <path id="path60" class="st11" d="M901,532.4h-12.3c-3,0-5.5-2.2-5.9-5.2c-3.7-25.2-23.5-45-48.7-48.7c-2.9-0.4-5.1-2.9-5.2-5.9 - v-12.3C868,462,899.3,493.3,901,532.4L901,532.4L901,532.4z"/> - <path id="path62" class="st11" d="M901.1,535.7c0,1.1,0,2.2-0.1,3.3h-12.3c-3,0-5.5,2.2-5.9,5.2c-3.7,25.2-23.5,45-48.7,48.7 - c-2.9,0.4-5.1,2.9-5.2,5.9v12.3c-1.1,0.1-2.2,0.1-3.3,0.1s-2.2,0-3.3-0.1v-12.3c0-3-2.2-5.5-5.2-5.9c-25.2-3.7-45-23.5-48.7-48.7 - c-0.4-2.9-2.9-5.1-5.9-5.2h-12.3c-0.1-1.1-0.1-2.2-0.1-3.3s0-2.2,0.1-3.3h12.3c3,0,5.5-2.2,5.9-5.2c3.7-25.2,23.5-45,48.7-48.7 - c2.9-0.4,5.1-2.9,5.2-5.9v-12.3c1.1-0.1,2.2-0.1,3.3-0.1s2.2,0,3.3,0.1v12.3c0,3,2.2,5.5,5.2,5.9c25.2,3.7,45,23.5,48.7,48.7 - c0.4,2.9,2.9,5.1,5.9,5.2H901C901,533.5,901.1,534.6,901.1,535.7L901.1,535.7z"/> - </g> - <path id="path72" d="M658.3,906.6h-7.9c-3.6,0-6.4-2.9-6.5-6.5V742.9c0-4.9,1.2-9.6,3.4-13.9l6.7-13v-79.2l-6.7-13 - c-2.2-4.3-3.4-9.1-3.4-14V340.1c0-4.9,1.2-9.6,3.4-13.9l6.7-13V234l-6.7-13c-2.2-4.3-3.4-9.1-3.4-14V71.2c0-3.6,2.9-6.4,6.5-6.5 - h7.9c3.6,0,6.4,2.9,6.5,6.5c1.1,276.3,1.2,552.6,0,828.9C664.7,903.7,661.8,906.6,658.3,906.6L658.3,906.6L658.3,906.6z - M650.4,65.7c-3,0-5.5,2.4-5.5,5.5V207c0,4.7,1.1,9.3,3.3,13.5l6.8,13.1c0,0.1,0.1,0.1,0.1,0.2v79.5c0,0.1,0,0.2-0.1,0.2 - l-6.8,13.1c-2.2,4.2-3.3,8.8-3.3,13.5v269.7c0,4.7,1.1,9.3,3.3,13.5l6.8,13.1c0,0.1,0.1,0.1,0.1,0.2v79.5c0,0.1,0,0.2-0.1,0.2 - l-6.8,13.1c-2.2,4.2-3.3,8.8-3.3,13.5v157.2c0,3,2.4,5.5,5.5,5.5h7.9c3,0,5.5-2.4,5.5-5.5V71.2c0-3-2.4-5.5-5.5-5.5L650.4,65.7 - L650.4,65.7z"/> - <path id="path74" d="M675.5,858.9h-11.3c-0.3,0-0.5-0.2-0.5-0.5l0,0L664.5,72c0-0.3,0.2-0.5,0.5-0.5h11.3c0.3,0-0.3,0.2-0.3,0.5 - v786.4C676,858.7,675.8,858.9,675.5,858.9z M665.6,857.9h9.4l0.8-785.4h-10.3l1,8.8c-0.6,256.1,4.5,511.8-1.1,768.3 - C665.3,852.3,665.6,855.1,665.6,857.9L665.6,857.9z"/> - <path id="path76" d="M787.4,1000.2H681.2c-3.6,0-6.5-2.9-6.5-6.5v-978c0-3.6,2.9-6.5,6.5-6.5h106.2 - c116.8,0,211.9,95.1,211.9,211.9v567.2C999.3,905.3,904.5,1000.2,787.4,1000.2L787.4,1000.2z M681.2,10.4c-3,0-5.5,2.4-5.5,5.5 - v978c0,3,2.4,5.5,5.5,5.5h106.2c116.3,0,210.9-94.6,210.9-210.9V221.1c0-116.3-94.6-210.9-210.9-210.9L681.2,10.4z"/> - <path id="path128" d="M654.6,314.3h-4.2c-3.8,0-6.9-3.1-7-7v-67.6c0-3.8,3.1-6.9,7-7h4.2c0.6,0,1,0.4,1,1l0,0v79.5 - C655.6,313.9,655.1,314.3,654.6,314.3L654.6,314.3z M650.4,234.8c-2.7,0-5,2.2-5,5v67.6c0,2.7,2.2,5,5,5h3.2v-77.5h-3.2V234.8z"/> - <path id="path130" d="M654.6,717.1h-4.2c-3.8,0-6.9-3.1-7-7v-67.6c0-3.8,3.1-6.9,7-7h4.2c0.6,0,1,0.4,1,1l0,0V716 - C655.6,716.6,655.1,717.1,654.6,717.1L654.6,717.1z M650.4,637.6c-2.7,0-5,2.2-5,5v67.6c0,2.7,2.2,5,5,5h3.2v-77.5L650.4,637.6 - L650.4,637.6z"/> - <path id="path1240" class="st12" d="M805.5,998.7c26.6-2.4,50.5-9.2,75.9-21.8c5.1-2.5,10-5.2,14.9-8 - c19.1-11.3,36.3-27.1,49.6-41.7c25.5-28,45.5-70.1,51.4-116.5c0.1-0.7,0.4-4.3,0.4-6.8c-0.1-6.9,0.7-20,0.8-38.3 - c0.7-77.5,1.1-244,1.1-376.4c0-101.2-0.3-182.5-1-188.9c-2.7-26.1-9.3-49.1-20.8-72.1c-31.8-63.9-93-107.4-164-116.7 - c-11.4-1.5-60.7-1.6-96.6-1.3c-7.7,0.1-14.1-0.1-20.1,0.1c-7.5,0.2-12.9-0.1-16.2,0.2c-1.7,0.2-3,0.8-3.2,1 - c-0.1,0.1-1.4,1.4-1.5,3.3c-0.3,5.2-0.3,17.1-0.4,38c0,4.2-0.1,8.8-0.1,13.8c0,4.9,0.1,10.1,0.1,15.8c-0.1,8.8,0.1,18.6,0.1,29.2 - c0,9.8-0.2,20.5,0,32c0.2,9,0.2,18.4,0.2,28.4c-0.1,15.1,0.6,31.1,0.3,49c-0.2,15,0.1,30,0,46.9c-0.1,11.1,0.1,22.6,0,34.6 - c-0.1,7.2,0,14.5-0.1,22.1c-0.5,52.1,0,112.2,0,180.3c0,254.3-0.3,376.1,0,435.2c0,7.7-0.2,14.5-0.1,20.2c0,2.2,0,4.3,0,6.2 - c0,3.3,0,6.6,0,9.3c0,2.9-0.1,5.8,0,8c0.1,5,0.1,8.1,0.3,10c0.3,2.8,1.3,3.6,1.6,3.8c0.2,0.2,1.3,0.9,3.1,1.1 - c4.5,0.4,14.6,0.3,27.1,0.2c9.9-0.1,21.3-0.2,32.8-0.2C772.7,998.8,805.8,999,805.5,998.7L805.5,998.7L805.5,998.7z"/> - <path id="path78" d="M771.7,763.2c-23.1,0-41.9-18.7-41.9-41.9s18.7-41.9,41.9-41.9c23.2,0,41.9,18.7,41.9,41.9l0,0 - C813.5,744.4,794.8,763.1,771.7,763.2z M771.7,680.4c-22.6,0-40.9,18.3-40.9,40.9s18.3,40.9,40.9,40.9c22.6,0,40.9-18.3,40.9-40.9 - l0,0C812.5,698.7,794.3,680.4,771.7,680.4z"/> - <path id="path88" class="st13" d="M838.9,203.2h-5.5l-7.6-10.9l-8,10.9h-5.4l10.6-16.3l-9.8-15.6h5.2l7.4,10.6l7.3-10.6h5 - l-9.8,15.4L838.9,203.2L838.9,203.2z"/> - <path id="path90" class="st13" d="M765.9,244.5l-11.6,20.6v11.4h-4.4V265l-11.6-20.5h5.3l6.4,11.7l2.1,3l2.4-2.6l6.4-12.1H765.9 - L765.9,244.5z"/> - <path id="path92" class="st13" d="M912.6,276.5h-4.7l-2.2-7l-12.4,0.3l-3.2,6.7h-4.5l9.9-35.2l6.7,3.2L912.6,276.5z M903.9,265.2 - l-4.9-14.6l-4.6,14.8L903.9,265.2L903.9,265.2z"/> - <path id="path132" d="M982.3,139.3h-0.2c-0.3-0.1-0.6-0.3-0.7-0.6C948.4,61,872.1,10.5,787.6,10.6h-54.2c-0.6,0-1-0.4-1-1l0,0V7 - c0-3.8,3.1-6.9,7-7h51.2C875.8,0.1,953,50.1,987.9,127.8c1.2,2.6,0.6,5.7-1.5,7.8L983,139C982.8,139.2,982.5,139.3,982.3,139.3 - L982.3,139.3z M734.4,8.6h53.2c84.7-0.1,161.3,50.2,195,128l2.4-2.4l0,0c1.5-1.4,1.9-3.6,1.1-5.5C951.5,51.7,875,2.1,790.6,2 - h-51.2c-2.7,0-5,2.2-5,5V8.6z"/> - <path id="path134" d="M733.2,133.7h-11.7c-2.2,0-4-1.8-4-4V118c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4v-11.7c0-2.2,1.8-4,4-4 - h11.7c1.1,0,2-0.9,2-2V82.8c0-2.2,1.8-4,4-4h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7 - c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7C737.2,131.9,735.4,133.7,733.2,133.7z M703.9,98.5c-1.1,0-2,0.9-2,2v11.7 - c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2v-11.7c0-2.2,1.8-4,4-4H751c1.1,0,2-0.9,2-2 - v-11.7c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4V82.8c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2v11.7c0,2.2-1.8,4-4,4H703.9z"/> - <path id="path136" d="M825.6,372.4c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 - C864,355.2,846.8,372.4,825.6,372.4z M825.6,297.5c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5c20.2,0,36.5-16.3,36.5-36.5 - C862,313.8,845.7,297.5,825.6,297.5z"/> - <path id="path138" d="M825.6,225.5c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 - C864,208.3,846.8,225.5,825.6,225.5z M825.6,150.6c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5c20.2,0,36.5-16.3,36.5-36.5 - C862,166.9,845.7,150.6,825.6,150.6z"/> - <path id="path140" d="M899,299c-21.2,0-38.5-17.2-38.5-38.5S877.7,222,899,222c21.3,0,38.5,17.2,38.5,38.5S920.3,298.9,899,299z - M899,224c-20.1,0-36.5,16.3-36.5,36.5S878.8,297,899,297c20.2,0,36.5-16.3,36.5-36.5S919.2,224,899,224z"/> - <path id="path142" d="M752.2,299c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 - C790.6,281.7,773.4,298.9,752.2,299z M752.2,224c-20.1,0-36.5,16.3-36.5,36.5s16.2,36.2,36.4,36.2s36.6-16,36.6-36.2 - C788.6,240.4,772.3,224,752.2,224z"/> - <path id="path144" d="M771.7,750.2c-16,0-28.9-13-28.9-28.9s13-28.9,28.9-28.9s28.9,13,28.9,28.9l0,0 - C800.6,737.3,787.7,750.2,771.7,750.2z M771.7,694.3c-14.9,0-26.9,12.1-26.9,26.9s12.1,26.9,26.9,26.9s26.9-12.1,26.9-26.9 - S786.6,694.4,771.7,694.3z"/> - <path id="path146" d="M762.5,533.4h-12.3c-0.6,0-1-0.4-1-1l0,0c1.7-39.6,33.4-71.3,73-73c0.3,0,0.5,0.1,0.7,0.3 - c0.2,0.2,0.3,0.4,0.3,0.7v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9C768.9,530.8,766,533.4,762.5,533.4z - M751.3,531.4h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2 - C783.9,463.5,753.4,494,751.3,531.4z"/> - <path id="path148" d="M822.3,612.1C822.3,612.1,822.2,612.1,822.3,612.1c-39.6-1.7-71.3-33.4-73-73c0-0.6,0.4-1,1-1h12.3 - c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9v12.3C823.3,611.6,822.8,612.1,822.3,612.1L822.3,612.1 - L822.3,612.1z M751.3,540c2.2,37.4,32.6,67.8,70,70v-11.2c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6 - c-0.3-2.5-2.4-4.3-4.9-4.3H751.3z"/> - <path id="path150" d="M828.9,612.1c-0.6,0-1-0.4-1-1l0,0v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9 - c0.5-3.4,3.4-6,6.9-6H901c0.6,0,1,0.4,1,1l0,0C900.2,578.7,868.6,610.3,828.9,612.1C829,612.1,829,612.1,828.9,612.1L828.9,612.1 - L828.9,612.1z M888.7,540c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9V610 - c37.4-2.2,67.8-32.6,70-70H888.7z"/> - <path id="path152" d="M901,533.4h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-12.3 - c0-0.3,0.1-0.5,0.3-0.7c0.2-0.2,0.5-0.3,0.7-0.3c39.6,1.7,71.3,33.4,73,73C902,532.9,901.6,533.3,901,533.4L901,533.4L901,533.4z - M829.9,461.4v11.2c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2 - C897.8,494,867.3,463.5,829.9,461.4z"/> - <path id="path154" d="M825.6,612.2c-1.2,0-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 - c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3c-0.5,0-1-0.4-1-0.9c-0.1-1.1-0.1-2.2-0.1-3.4s0-2.3,0.1-3.4 - c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1 - c2.3-0.1,4.5-0.1,6.8,0c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3H901 - c0.5,0,1,0.4,1,0.9c0.1,1.1,0.1,2.2,0.1,3.4s0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3 - c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1C827.9,612.1,826.8,612.2,825.6,612.2L825.6,612.2 - L825.6,612.2z M823.3,610.1c1.5,0.1,3.1,0.1,4.7,0v-11.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6 - h11.3v-4.6h-11.4c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3c-1.5-0.1-3.1-0.1-4.7,0v11.3 - c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3v4.6h11.3c3.5,0,6.4,2.6,6.9,6 - c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9L823.3,610.1L823.3,610.1L823.3,610.1z"/> - <path id="path1462" class="st4" d="M743,295.5c-6.3-1.7-11.3-3.9-16.5-9.1c-7.7-7.7-11.3-17-10.8-27.7c0.6-13.5,8-25,20-31 - c10.5-5.2,22.6-5.3,32.7-0.3c7.9,3.9,12.8,10,16.6,18c5.2,10.8,4.2,24.4-2.4,34.6c-5,7.6-13.5,13.7-22,15.7 - C755.7,296.8,747.8,296.7,743,295.5L743,295.5L743,295.5z"/> - <path id="path1464" class="st4" d="M819.4,222.9c0-0.3-3.7-0.9-5.7-1.6c-3-1.1-5.9-2.6-8-4c-6.9-4.6-11.7-10.9-14.5-18.9 - c-1.3-3.8-2.1-5.2-2.1-11.4s0.7-7.2,2.1-11c3.9-11.1,11.8-19.1,22.8-23c4.2-1.5,5.2-2.3,11.6-2.3c6.4,0,7.3,0.8,11.6,2.3 - c14.5,5.2,24,17.6,24.7,32.9c0.5,10.6-2.7,19.5-10.2,26.9c-7.6,7.5-14.4,10.7-24.6,11C823.6,223.7,820.2,223,819.4,222.9 - L819.4,222.9L819.4,222.9z"/> - <path id="path1466" class="st4" d="M891.7,296.5c-6.7-1-15.6-5.8-20.8-12.9c-3-4.1-6.2-10.7-7.3-15.7 - c-2.8-13.2,2.6-27.9,13.3-35.9c20-15.1,48.1-6.7,56.4,17c1.9,5.3,3,13.3,1.7,19.2c-1.5,6.8-4.9,12.9-10.2,18.2 - c-5.3,5.2-9.9,8-16.4,9.6C903.5,297.2,896.3,297.2,891.7,296.5L891.7,296.5L891.7,296.5z"/> - <path id="path1468" class="st4" d="M820.4,369.9c-8.1-1.3-14.2-4-20.6-10.3c-3.8-3.7-5.4-6.2-7.2-10c-7.6-15.8-3-33.1,10.8-44.1 - c6.4-5.1,13.6-7.6,22.1-7.6c29.9,0,46.7,33.8,28.7,58c-2.8,3.8-6.3,7.5-10.5,9.8C836.7,369.8,828.1,371.1,820.4,369.9L820.4,369.9 - L820.4,369.9z"/> - <path id="path1500" class="st6" d="M823.6,602.7c-0.3-8.9-0.8-9.7-10.1-11.9c-21.1-4.8-37.2-20.1-42.6-41.3 - c-0.6-2.2-1.1-5.2-1.1-5.9s-1-2.2-2.2-3.5l-2.3-2l-6.9-0.3h-7v-4h5.9c6.6,0,9.9-1,11.2-3.4c0.4-0.8,1.5-3.9,2-7 - c2.7-16.3,14.9-30.7,29.5-38c4.7-2.4,11.4-4.6,15.5-5.4c6.6-1.1,8.2-3.6,8.2-12.8v-5.9h4v6.9c0,6.6,0.1,7,1.8,8.8 - c1.5,1.6,3.9,1.7,9.5,3.1c20.4,5,35.3,20.5,40.7,40.1c0.8,2.9,1.8,6.4,2.2,7.8c1.2,4.3,3.8,5.6,11.7,5.6h6.5v4h-6.6 - c-7,0-9.9,0.8-10.8,3.1c-0.3,0.7-1.3,4.6-2.1,8c-5.4,21.4-21.3,36.7-42.1,41.5c-9.9,2.3-10.7,3.2-10.7,12.8v6.3h-3.8L823.6,602.7 - L823.6,602.7z"/> - <path id="path1504" class="st7" d="M752.2,526.5c1.8-15.3,8.9-31.2,20.4-43c11.9-12.2,27.5-19.2,45.7-21.6l2.8-0.4v6.2 - c0,7.9-0.1,8.9-6.8,10.2c-22.3,4.6-41.8,22.1-46.1,44.2c-1.8,9.1-2.4,9.1-11.1,9.1h-5.4L752.2,526.5L752.2,526.5z"/> - <path id="path1506" class="st7" d="M811.2,608.2c-32.2-6.9-55.7-32.9-59.4-65.6l-0.2-2.1h6.4c6.9,0,6.8,0,7.8,1 - c1.3,1.4,1.5,3.8,2.4,7.9c4.8,21.2,24.2,39.1,46.6,44c6.9,1.5,6.4,2.7,6.4,10.3v5.9l-2.1,0C817.8,609.5,814.3,608.9,811.2,608.2 - L811.2,608.2L811.2,608.2z"/> - <path id="path1508" class="st7" d="M830,603c-0.3-7.3,1-8.2,5.3-9c4.8-0.8,11.1-2.9,16.3-5.5c15.8-7.7,27.1-22,31.6-39.9 - c1-4,1.6-7.2,2.2-7.7c0.7-0.5,3.9-0.5,7.8-0.5h6.2l-0.4,3.8c-3.8,33.2-31.8,61.3-64.8,65l-4,0.5L830,603L830,603z"/> - <path id="path1510" class="st7" d="M885.4,529.8c-1.2-1.1-1.3-3-2.1-6.7c-4.4-20.5-19-36.6-39.3-43.5c-2.4-0.8-5.8-1.7-7.5-2 - c-1.9-0.3-3.9-0.6-4.8-1.5c-1.4-1.4-1.3-2.6-1.3-8.2v-6.2l2.5,0.3c19,2.5,32.7,9.2,45.4,22.1c10.4,10.5,17.4,23.8,20.2,38.1 - c0.6,2.9,1,6.1,1,7.1v1.9h-5.9C888.3,531.3,886.8,531.1,885.4,529.8L885.4,529.8L885.4,529.8z"/> - <path id="path1512" class="st4" d="M720.3,131c-0.5-0.6-0.6-1.7-0.6-7.6v-6.9l-1.1-1.2l-1.1-1.2l-7.1-0.1 - c-4.1-0.1-6.5,0.5-7.6-0.2c-1.3-0.8-0.9-3.1-0.9-7.6s0.1-6.3,0.5-6.8c0.5-0.7,1.1-0.7,7.3-0.8c3.7-0.1,7.1-0.2,7.6-0.3 - c0.5-0.1,1.2-0.7,1.6-1.4c0.7-1.1,0.7-1.9,0.7-7.8c0-3.8,0.2-6.8,0.4-7.3c0.4-0.7,0.9-0.7,7.3-0.7s7,0.1,7.3,0.7 - c0.2,0.4,0.4,3.5,0.4,7.3c0,5.8,0.1,6.7,0.7,7.8c0.4,0.7,1.1,1.3,1.6,1.4s3.9,0.2,7.6,0.3c6.2,0.1,6.8,0.2,7.3,0.8 - c0.4,0.6,0.5,2.2,0.5,6.8s0.7,7.2-0.9,7.9c-1.2,0.5-3.8-0.1-7.6-0.1l-7.1,0.1l-1.1,1.2l-1.1,1.2v6.9c0,6.2-0.1,7-0.7,7.6 - c-0.6,0.5-1.7,0.6-7.1,0.6C721.7,131.7,720.9,131.6,720.3,131L720.3,131L720.3,131z"/> - <path id="path1514" class="st7" d="M766.6,726.4v-5.3h10.5v10.5h-10.5V726.4L766.6,726.4z"/> - <path id="path1518" class="st7" d="M766,761.6c-14.8-2.3-27.2-12.3-32.4-26.2c-7.5-20,2.3-43,22-51.4c6.1-2.6,8-3.5,15.7-3.5 - c6.5,0,7.9,0.6,11.3,1.6c14.4,4.4,24.6,14.9,28.6,29.4c0.8,2.9,1.1,5.2,1.1,10.7c0,5.5-0.3,6.2-1.1,9c-4.8,17.7-20,30-38.1,30.5 - C770.5,761.9,767.4,761.8,766,761.6L766,761.6L766,761.6z M778.1,749.7c11.9-2.6,21.3-13,22.5-25.1c2.2-21.5-18.1-37.7-38.5-30.7 - c-4.2,1.4-7,3.3-10.8,7.1c-2.8,2.8-3.8,4.1-5.3,7.2c-2.6,5.1-3.3,8.7-3,14.6c0.3,6.8,2.2,11.7,6.3,16.9 - C755.9,748,767.4,752,778.1,749.7L778.1,749.7L778.1,749.7z"/> - <path id="path1520" class="st9" d="M648.2,714.3c-1.3-0.8-2.3-0.9-2.5-5.3c-0.2-4.3,0.1-13.5,0.1-32.6c0-32.8-0.5-35.2,0.6-36.3 - c0.1-0.1,0.1-0.2,0.2-0.2c1.1-1.5,2.7-2.1,4.9-2.1h1.9V715h-2C650.1,715,648.9,714.8,648.2,714.3L648.2,714.3L648.2,714.3z"/> - <path id="path1522" class="st9" d="M649.6,312.1c-0.5-0.1-1.3-0.4-1.8-0.8c-0.8-0.5-1.5-0.8-1.8-2.7c-0.5-3.2-0.2-11.4-0.2-35.1 - c0-19.3-0.5-28.4-0.4-32.5c0.1-2.7,0.8-3.2,1-3.6c0.9-1.6,2.4-2.3,4.8-2.3h2.1v77.3l-1.4,0C651.2,312.3,650.1,312.2,649.6,312.1 - L649.6,312.1L649.6,312.1z"/> - <path id="path1524" class="st8" d="M978,126.4c-10.1-20.6-21.9-37.2-38.2-53.9c-34.8-35.7-78.7-57-129.6-63.1 - c-5.1-0.6-13.1-0.8-40.9-1l-34.6-0.2V6.9c0-1,0-3,1.4-3.7l2.1-1.1l30.4,0.2c16.9,0.1,33.6,0.2,37,0.5 - c50.8,3.9,97.1,24.6,133.3,59.5c17.6,16.9,31.5,35.7,42.8,57.9c4.3,8.4,4.9,10.1,4.2,11.8c-0.4,1.1-2.7,4.2-3.2,4.1 - C982.6,136.1,980.5,131.5,978,126.4L978,126.4L978,126.4z"/> - <path id="path1530" class="st4" d="M646.8,903.8c-0.6-0.5-1.7-1.2-1.6-5c0.3-6.7-0.2-27.2-0.1-79.1l0.3-82.6l2.6-6.5l7.9-15 - l0.1-39.4l-0.4-39.6l-7.1-13.2l-2.9-7.7l-0.4-7.3l0.1-48l-0.1-22.1l0.1-34.1l0.1-10.5V483l-0.1-8.6l0-25.6v-19.4l-0.2-27.5 - l0.1-21.6l0-11l0.1-19.6l0-10.2l0.5-5.7l1.9-5.8l3-5.5l5.3-9.8v-39.5l-0.4-39.6l-5.3-9.8l-2.7-5.3l-1.8-5.1l-0.7-7.6l0.2-13.3 - l-0.1-12.2v-11.1l-0.1-10.9l0-9.9V142l0-2.9l0.1-17.4l0-21.5V89.4l-0.1-8.4l0-5.1l-0.2-5.9l2.2-3.1l3.2-1.1l3.2,0.1 - c1.4,0,2.7,0.1,3.9,0.2c0.9,0.1,1.8,0.1,2.6,0.4c1,0.4,1.7,1,2,1.3c0.2,0.2,1.2,1.7,1.3,4.8c0.1,2.1,0,4.7,0,8.3 - c0,4.1,0.1,9.7,0,16.4c-0.1,10,0,23.1-0.1,40.4c0,13.2,0.2,28.7,0.1,47c-0.2,62.8-0.2,158-0.2,301l0,416.2l-1.3,1.8 - C660.4,906.2,649.5,906.5,646.8,903.8L646.8,903.8L646.8,903.8z"/> - <path id="path1532" class="st4" d="M665.8,465.1V72.7h8.6v784.7h-8.6V465.1L665.8,465.1z"/> - <ellipse id="path3879" class="st14" cx="771.6" cy="721.1" rx="40.3" ry="40.3"/> - <ellipse id="path3881" class="st15" cx="771.7" cy="721.4" rx="34.1" ry="34.5"/> - <path id="path96" d="M771.7,694.3l-26.8,26.4h7.7v22.5h38.5v-22.5h7.2L771.7,694.3L771.7,694.3z M779.5,735.2h-15.2v-14.5h15.2 - V735.2z"/> - </g> -</g> -</svg> diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg deleted file mode 100644 index adb6e1e1..00000000 --- a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg +++ /dev/null @@ -1,185 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 1000.4 356.1" style="enable-background:new 0 0 1000.4 356.1;" xml:space="preserve"> -<style type="text/css"> - .st0{opacity:0.1;} - .st1{fill:#FF5F55;} - .st2{fill:#FF5F53;} - .st3{fill:#FFFFFF;} - .st4{fill:#1A1A1A;stroke:#000000;} - .st5{fill:#1A1A1A;stroke:#1A1A1A;} - .st6{fill:#1A1A1A;stroke:#4D4D4D;} - .st7{stroke:#000000;} - .st8{stroke:#4D4D4D;} - .st9{fill:#999595;stroke:#000000;stroke-width:2.39;stroke-linecap:round;stroke-linejoin:round;} - .st10{fill:#3A3D40;stroke:#000000;stroke-width:2.73;stroke-linecap:round;stroke-linejoin:round;} -</style> -<g id="g315"> - <g id="g64" class="st0"> - <path id="path36" class="st1" d="M766.5,11.2H687V7c0-3.3,2.7-6,6-6h67.6c3.3,0,6,2.7,6,6L766.5,11.2L766.5,11.2z"/> - <path id="path38" class="st1" d="M363.8,11.2h-79.5V7c0-3.3,2.7-6,6-6h67.6c3.3,0,6,2.7,6,6L363.8,11.2L363.8,11.2z"/> - <path id="path40" class="st1" d="M865.5,342.3l-3.4-3.4c78-33.2,128.7-109.8,128.7-194.7V90h2.6c3.3,0,6,2.7,6,6v51.2 - c0,84.8-49.8,161.7-127.2,196.4C869.9,344.6,867.3,344.1,865.5,342.3L865.5,342.3L865.5,342.3z"/> - <path id="path42" class="st1" d="M905.9,92.8h11.7c1.6,0,3-1.3,3-3V78.1c0-1.6-1.3-3-3-3l0,0h-11.7c-1.6,0-3-1.3-3-3V60.4 - c0-1.6-1.3-3-3-3l0,0h-11.7c-1.6,0-3,1.3-3,3v11.7c0,1.6-1.3,3-3,3l0,0h-11.7c-1.6,0-3,1.3-3,3v11.7c0,1.6,1.3,3,3,3l0,0h11.7 - c1.6,0,3,1.3,3,3v11.7c0,1.6,1.3,3,3,3l0,0h11.7c1.6,0,3-1.3,3-3V95.8C902.9,94.1,904.3,92.8,905.9,92.8L905.9,92.8z"/> - <circle id="circle44" class="st1" cx="666.5" cy="182.2" r="37.5"/> - <circle id="circle46" class="st1" cx="813.3" cy="182.2" r="37.5"/> - <circle id="circle48" class="st1" cx="739.9" cy="255.6" r="37.5"/> - <circle id="circle50" class="st1" cx="739.9" cy="108.8" r="37.5"/> - <circle id="circle52" class="st1" cx="279.1" cy="128.3" r="27.9"/> - <path id="path54" class="st1" d="M540.1,178.9h-12.3c-3,0-5.5-2.2-5.9-5.2c-3.7-25.2-23.5-45-48.7-48.7c-2.9-0.4-5.1-2.9-5.2-5.9 - v-12.3C507.1,108.5,538.4,139.8,540.1,178.9L540.1,178.9L540.1,178.9z"/> - <path id="path56" class="st1" d="M401.6,178.9h-12.3c1.7-39.1,33-70.3,72-72.1v12.3c0,3-2.2,5.5-5.2,5.9 - c-25.2,3.7-45,23.5-48.7,48.7C407.1,176.7,404.6,178.9,401.6,178.9L401.6,178.9L401.6,178.9z"/> - <path id="path58" class="st1" d="M461.4,257.6c-39.1-1.7-70.3-33-72-72.1h12.3c3,0,5.5,2.2,5.9,5.2c3.7,25.2,23.5,45,48.7,48.7 - c2.9,0.4,5.1,2.9,5.2,5.9L461.4,257.6L461.4,257.6z"/> - <path id="path60" class="st1" d="M468,257.6v-12.3c0-3,2.2-5.5,5.2-5.9c25.2-3.7,45-23.5,48.7-48.7c0.4-2.9,2.9-5.1,5.9-5.2h12.3 - C538.4,224.6,507.1,255.9,468,257.6L468,257.6L468,257.6z"/> - <path id="path62" class="st1" d="M464.7,257.7c-1.1,0-2.2,0-3.3-0.1v-12.3c0-3-2.2-5.5-5.2-5.9c-25.2-3.7-45-23.5-48.7-48.7 - c-0.4-2.9-2.9-5.1-5.9-5.2h-12.3c-0.1-1.1-0.1-2.2-0.1-3.3s0-2.2,0.1-3.3h12.3c3,0,5.5-2.2,5.9-5.2c3.7-25.2,23.5-45,48.7-48.7 - c2.9-0.4,5.1-2.9,5.2-5.9v-12.3c1.1-0.1,2.2-0.1,3.3-0.1s2.2,0,3.3,0.1v12.3c0,3,2.2,5.5,5.2,5.9c25.2,3.7,45,23.5,48.7,48.7 - c0.4,2.9,2.9,5.1,5.9,5.2h12.3c0.1,1.1,0.1,2.2,0.1,3.3s0,2.2-0.1,3.3h-12.3c-3,0-5.5,2.2-5.9,5.2c-3.7,25.2-23.5,45-48.7,48.7 - c-2.9,0.4-5.1,2.9-5.2,5.9v12.3C466.9,257.6,465.8,257.7,464.7,257.7L464.7,257.7z"/> - </g> - <path id="path72" d="M93.8,14.9V7c0-3.6,2.9-6.4,6.5-6.5h157.2c4.9,0,9.6,1.2,13.9,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,14-3.4 - h269.7c4.9,0,9.6,1.2,13.9,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,14-3.4h135.8c3.6,0,6.4,2.9,6.5,6.5v7.9c0,3.6-2.9,6.4-6.5,6.5 - c-276.3,1.1-552.6,1.2-828.9,0C96.7,21.3,93.8,18.4,93.8,14.9L93.8,14.9L93.8,14.9z M934.7,7c0-3-2.4-5.5-5.5-5.5H793.4 - c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.1,0.1-0.2,0.1h-79.5c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8c-4.2-2.2-8.8-3.3-13.5-3.3H390.6 - c-4.7,0-9.3,1.1-13.5,3.3L364,11.6c-0.1,0-0.1,0.1-0.2,0.1h-79.5c-0.1,0-0.2,0-0.2-0.1L271,4.8c-4.2-2.2-8.8-3.3-13.5-3.3H100.3 - c-3,0-5.5,2.4-5.5,5.5v7.9c0,3,2.4,5.5,5.5,5.5h828.9c3,0,5.5-2.4,5.5-5.5L934.7,7L934.7,7z"/> - <path id="path74" d="M141.5,32.1V20.8c0-0.3,0.2-0.5,0.5-0.5l0,0l786.4,0.8c0.3,0,0.5,0.2,0.5,0.5v11.3c0,0.3-0.2-0.3-0.5-0.3H142 - C141.7,32.6,141.5,32.4,141.5,32.1z M142.5,22.2v9.4L928,32.4V22.1l-8.8,1c-256.1-0.6-511.8,4.5-768.3-1.1 - C148.1,21.9,145.3,22.2,142.5,22.2L142.5,22.2z"/> - <path id="path76" d="M0.2,144V37.8c0-3.6,2.9-6.5,6.5-6.5h978c3.6,0,6.5,2.9,6.5,6.5V144c0,116.8-95.1,211.9-211.9,211.9H212.1 - C95.1,355.9,0.2,261.1,0.2,144L0.2,144L0.2,144z M990.1,37.8c0-3-2.4-5.5-5.5-5.5H6.6c-3,0-5.5,2.4-5.5,5.5V144 - c0,116.3,94.6,210.9,210.9,210.9h567.3c116.3,0,210.9-94.6,210.9-210.9L990.1,37.8L990.1,37.8z"/> - <path id="path128" d="M686.1,11.2V7c0-3.8,3.1-6.9,7-7h67.6c3.8,0,6.9,3.1,7,7v4.2c0,0.6-0.4,1-1,1l0,0h-79.5 - C686.5,12.2,686.1,11.7,686.1,11.2L686.1,11.2z M765.6,7c0-2.7-2.2-5-5-5H693c-2.7,0-5,2.2-5,5v3.2h77.5L765.6,7L765.6,7z"/> - <path id="path130" d="M283.3,11.2V7c0-3.8,3.1-6.9,7-7h67.6c3.8,0,6.9,3.1,7,7v4.2c0,0.6-0.4,1-1,1l0,0h-79.5 - C283.8,12.2,283.3,11.7,283.3,11.2L283.3,11.2z M362.8,7c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2h77.5L362.8,7L362.8,7z"/> - <path id="path1240" class="st2" d="M1.8,162.1c2.4,26.6,9.2,50.5,21.8,76c2.5,5.1,5.2,10,8,14.9c11.3,19.1,27.1,36.3,41.7,49.6 - c28,25.5,70.1,45.5,116.5,51.4c0.7,0.1,4.3,0.4,6.8,0.4c6.9-0.1,20,0.7,38.3,0.8c77.5,0.7,244,1.1,376.4,1.1 - c101.2,0,182.5-0.3,188.9-1c26.1-2.7,49.1-9.3,72.1-20.8c63.9-31.8,107.4-93,116.7-164c1.5-11.4,1.6-60.7,1.3-96.6 - c-0.1-7.7,0.1-14.1-0.1-20.1c-0.2-7.5,0.1-12.9-0.2-16.2c-0.2-1.7-0.8-3-1-3.2c-0.1-0.1-1.4-1.4-3.3-1.5c-5.2-0.3-17.1-0.3-38-0.4 - c-4.2,0-8.8-0.1-13.8-0.1c-4.9,0-10.1,0.1-15.8,0.1c-8.8,0-18.6,0.1-29.2,0.1c-9.8,0-20.5-0.2-32,0c-9,0.2-18.4,0.2-28.4,0.2 - c-15.1-0.1-31.1,0.6-49,0.3c-15-0.2-30,0.1-46.9,0c-11.1-0.1-22.6,0.1-34.6,0c-7.2-0.1-14.5,0-22.1-0.1c-52.1-0.5-112.2,0-180.3,0 - c-254.3,0-376.1-0.3-435.2,0c-7.7,0-14.5-0.2-20.2-0.1c-2.2,0-4.3,0-6.2,0c-3.3,0-6.6,0-9.3,0c-2.9,0-5.8-0.1-8,0 - c-5,0.1-8.1,0.1-10,0.3c-2.8,0.3-3.6,1.3-3.8,1.6c-0.2,0.2-0.9,1.3-1.1,3.1c-0.4,4.5-0.3,14.6-0.2,27.1c0.1,9.9,0.2,21.3,0.2,32.8 - C1.7,129.3,1.5,162.4,1.8,162.1L1.8,162.1L1.8,162.1z"/> - <path id="path78" d="M237.2,128.3c0-23.1,18.7-41.9,41.9-41.9s41.9,18.7,41.9,41.9s-18.7,41.9-41.9,41.9l0,0 - C256,170.1,237.3,151.4,237.2,128.3z M320,128.3c0-22.6-18.3-40.9-40.9-40.9s-40.9,18.3-40.9,40.9s18.3,40.9,40.9,40.9l0,0 - C301.7,169.1,320,150.9,320,128.3z"/> - <path id="path92" class="st3" d="M723.9,269.2v-4.7l7-2.2l-0.3-12.4l-6.7-3.2v-4.5l35.2,9.9l-3.2,6.7L723.9,269.2z M735.2,260.5 - l14.6-4.9L735,251L735.2,260.5L735.2,260.5z"/> - <path id="path132" d="M861.1,338.9v-0.2c0.1-0.3,0.3-0.6,0.6-0.7c77.7-33,128.2-109.3,128.1-193.8V90c0-0.6,0.4-1,1-1l0,0h2.6 - c3.8,0,6.9,3.1,7,7v51.2c-0.1,85.2-50.1,162.4-127.8,197.3c-2.6,1.2-5.7,0.6-7.8-1.5l-3.4-3.4C861.2,339.4,861.1,339.1,861.1,338.9 - L861.1,338.9z M991.8,91v53.2c0.1,84.7-50.2,161.3-128,195l2.4,2.4l0,0c1.4,1.5,3.6,1.9,5.5,1.1c77-34.6,126.6-111.1,126.7-195.5 - V96c0-2.7-2.2-5-5-5H991.8z"/> - <path id="path134" d="M866.7,89.8V78.1c0-2.2,1.8-4,4-4h11.7c1.1,0,2-0.9,2-2V60.4c0-2.2,1.8-4,4-4h11.7c2.2,0,4,1.8,4,4v11.7 - c0,1.1,0.9,2,2,2h11.5c2.2,0,4,1.8,4,4v11.7c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7c0,2.2-1.8,4-4,4h-11.7c-2.2,0-4-1.8-4-4 - V95.8c0-1.1-0.9-2-2-2h-11.7C868.5,93.8,866.7,92,866.7,89.8z M901.9,60.5c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2v11.7 - c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2 - V95.9c0-2.2,1.8-4,4-4h11.7c1.1,0,2-0.9,2-2V78.2c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4V60.5z"/> - <path id="path136" d="M628,182.2c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 - C645.2,220.6,628,203.4,628,182.2z M702.9,182.2c0-20.1-16.3-36.5-36.5-36.5s-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5 - C686.6,218.6,702.9,202.3,702.9,182.2z"/> - <path id="path138" d="M774.9,182.2c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 - C792.1,220.6,774.9,203.4,774.9,182.2z M849.8,182.2c0-20.1-16.3-36.5-36.5-36.5c-20.2,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5 - C833.5,218.6,849.8,202.3,849.8,182.2z"/> - <path id="path140" d="M701.4,255.6c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5S701.5,276.9,701.4,255.6z - M776.4,255.6c0-20.1-16.3-36.5-36.5-36.5s-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5S776.4,275.8,776.4,255.6z"/> - <path id="path142" d="M701.4,108.8c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 - C718.7,147.2,701.5,130,701.4,108.8z M776.4,108.8c0-20.1-16.3-36.5-36.5-36.5s-36.2,16.2-36.2,36.4s16,36.6,36.2,36.6 - C760,145.2,776.4,128.9,776.4,108.8z"/> - <path id="path144" d="M250.2,128.3c0-16,13-28.9,28.9-28.9s28.9,13,28.9,28.9s-13,28.9-28.9,28.9l0,0 - C263.1,157.2,250.2,144.3,250.2,128.3z M306.1,128.3c0-14.9-12.1-26.9-26.9-26.9s-26.9,12.1-26.9,26.9s12.1,26.9,26.9,26.9 - S306,143.2,306.1,128.3z"/> - <path id="path146" d="M467,119.1v-12.3c0-0.6,0.4-1,1-1l0,0c39.6,1.7,71.3,33.4,73,73c0,0.3-0.1,0.5-0.3,0.7s-0.4,0.3-0.7,0.3 - h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9C469.6,125.5,467,122.6,467,119.1z M469,107.9v11.2 - c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3H539C536.9,140.5,506.4,110,469,107.9z"/> - <path id="path148" d="M388.3,178.9C388.3,178.9,388.3,178.8,388.3,178.9c1.7-39.6,33.4-71.3,73-73c0.6,0,1,0.4,1,1v12.3 - c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-12.3C388.8,179.9,388.3,179.4,388.3,178.9L388.3,178.9 - L388.3,178.9z M460.4,107.9c-37.4,2.2-67.8,32.6-70,70h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6 - c2.5-0.3,4.3-2.4,4.3-4.9V107.9z"/> - <path id="path150" d="M388.3,185.5c0-0.6,0.4-1,1-1l0,0h12.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9 - c3.4,0.5,6,3.4,6,6.9v12.3c0,0.6-0.4,1-1,1l0,0C421.7,256.8,390.1,225.2,388.3,185.5C388.3,185.6,388.3,185.6,388.3,185.5 - L388.3,185.5L388.3,185.5z M460.4,245.3c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-11.2 - c2.2,37.4,32.6,67.8,70,70V245.3z"/> - <path id="path152" d="M467,257.6v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h12.3 - c0.3,0,0.5,0.1,0.7,0.3s0.3,0.5,0.3,0.7c-1.7,39.6-33.4,71.3-73,73C467.5,258.6,467.1,258.2,467,257.6L467,257.6L467,257.6z - M539,186.5h-11.2c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v11.2 - C506.4,254.4,536.9,223.9,539,186.5z"/> - <path id="path154" d="M388.2,182.2c0-1.2,0-2.3,0.1-3.4c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3 - c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1c1.1-0.1,2.2-0.1,3.4-0.1s2.3,0,3.4,0.1 - c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3c0.5,0,1,0.4,1,0.9 - c0.1,2.3,0.1,4.5,0,6.8c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9 - v12.3c0,0.5-0.4,1-0.9,1c-1.1,0.1-2.2,0.1-3.4,0.1s-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 - c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3c-0.5,0-1-0.4-1-0.9C388.3,184.5,388.2,183.4,388.2,182.2L388.2,182.2 - L388.2,182.2z M390.3,179.9c-0.1,1.5-0.1,3.1,0,4.7h11.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9 - v11.3h4.6v-11.4c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h11.3c0.1-1.5,0.1-3.1,0-4.7h-11.3 - c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3h-4.6V119c0,3.5-2.6,6.4-6,6.9 - c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3V179.9L390.3,179.9z"/> - <path id="path1462" class="st4" d="M705,99.6c1.7-6.3,3.9-11.3,9.1-16.5c7.7-7.7,17-11.3,27.7-10.8c13.5,0.6,25,8,31,20 - c5.2,10.5,5.3,22.5,0.3,32.7c-4,7.9-10,12.8-18,16.6c-10.8,5.2-24.4,4.2-34.6-2.4c-7.7-5-13.7-13.5-15.7-22 - C703.6,112.3,703.7,104.4,705,99.6L705,99.6L705,99.6z"/> - <path id="path1464" class="st4" d="M777.6,176c0.3,0,0.9-3.7,1.6-5.7c1.1-3,2.6-5.9,4-8c4.6-6.9,10.9-11.7,18.9-14.5 - c3.8-1.3,5.2-2.1,11.5-2.1s7.2,0.7,11,2.1c11.1,3.9,19.1,11.8,23,22.8c1.5,4.2,2.3,5.2,2.3,11.6s-0.8,7.3-2.3,11.6 - c-5.2,14.5-17.6,24-32.9,24.7c-10.6,0.5-19.5-2.7-26.9-10.2c-7.5-7.6-10.7-14.4-11-24.6C776.7,180.2,777.4,176.7,777.6,176 - L777.6,176L777.6,176z"/> - <path id="path1466" class="st4" d="M704,248.2c1-6.7,5.8-15.6,12.9-20.8c4.1-3,10.7-6.2,15.7-7.3c13.2-2.8,27.9,2.6,35.9,13.3 - c15.2,20,6.7,48.1-17,56.4c-5.3,1.9-13.3,3-19.2,1.7c-6.8-1.5-12.9-4.9-18.2-10.2c-5.2-5.3-8-9.9-9.6-16.4 - C703.3,260.1,703.3,252.9,704,248.2L704,248.2L704,248.2z"/> - <path id="path1468" class="st4" d="M630.6,177c1.3-8.1,4-14.2,10.3-20.6c3.7-3.8,6.2-5.4,10-7.2c15.8-7.6,33.1-3,44.1,10.8 - c5.1,6.4,7.6,13.6,7.6,22.1c0,29.9-33.8,46.7-58,28.7c-3.8-2.8-7.5-6.3-9.8-10.5C630.7,193.3,629.4,184.7,630.6,177L630.6,177 - L630.6,177z"/> - <path id="path1500" class="st5" d="M397.8,180.2c8.9-0.3,9.7-0.8,11.9-10.1c4.8-21.1,20.1-37.2,41.3-42.6c2.2-0.5,5.2-1.1,5.9-1.1 - s2.2-1,3.5-2.2l2-2.3l0.3-6.9v-7h4v5.9c0,6.6,1,9.9,3.4,11.2c0.8,0.4,3.9,1.5,7,2c16.3,2.7,30.7,14.9,38,29.5 - c2.4,4.7,4.6,11.4,5.4,15.5c1.1,6.6,3.6,8.2,12.8,8.2h5.9v4H532c-6.6,0-7,0.1-8.8,1.8c-1.6,1.5-1.7,3.9-3.1,9.5 - c-5,20.4-20.5,35.3-40.2,40.7c-2.9,0.8-6.4,1.8-7.8,2.2c-4.3,1.2-5.6,3.8-5.6,11.7v6.5h-4V250c0-7-0.8-9.9-3.1-10.8 - c-0.7-0.3-4.6-1.3-8-2.1c-21.4-5.4-36.7-21.3-41.5-42.1c-2.3-10-3.2-10.7-12.8-10.7h-6.3v-3.8L397.8,180.2L397.8,180.2z"/> - <path id="path1504" class="st6" d="M473.9,108.8c15.3,1.8,31.2,8.9,43,20.4c12.2,12,19.2,27.5,21.6,45.7l0.4,2.8h-6.2 - c-7.9,0-8.9-0.1-10.2-6.8c-4.6-22.3-22.1-41.8-44.2-46.1c-9.1-1.8-9.1-2.4-9.1-11.1v-5.4L473.9,108.8L473.9,108.8z"/> - <path id="path1506" class="st6" d="M392.2,167.8c6.9-32.2,32.9-55.7,65.6-59.4l2.1-0.2v6.4c0,6.9,0,6.8-1,7.8 - c-1.4,1.3-3.8,1.5-7.9,2.4c-21.2,4.8-39.1,24.2-44,46.6c-1.5,6.9-2.7,6.4-10.3,6.4h-5.9l0-2.1C391,174.4,391.5,170.9,392.2,167.8 - L392.2,167.8L392.2,167.8z"/> - <path id="path1508" class="st6" d="M397.4,186.6c7.3-0.3,8.2,1,9,5.3c0.8,4.8,2.9,11.1,5.5,16.3c7.7,15.8,22,27.1,39.9,31.6 - c4,1,7.2,1.6,7.7,2.2c0.5,0.7,0.5,3.9,0.5,7.8v6.2l-3.8-0.4c-33.2-3.8-61.3-31.8-65-64.8l-0.5-4L397.4,186.6L397.4,186.6z"/> - <path id="path1510" class="st6" d="M470.6,242c1.1-1.2,3-1.3,6.7-2.1c20.5-4.4,36.6-19,43.5-39.3c0.8-2.4,1.7-5.8,2-7.5 - c0.3-1.9,0.6-3.9,1.5-4.8c1.4-1.4,2.6-1.3,8.2-1.3h6.2l-0.3,2.5c-2.5,19-9.2,32.7-22.1,45.4c-10.6,10.4-23.8,17.4-38.2,20.2 - c-2.9,0.5-6.1,1-7.1,1h-1.9v-5.9C469.2,244.9,469.3,243.4,470.6,242L470.6,242L470.6,242z"/> - <path id="path1512" class="st4" d="M869.4,76.9c0.6-0.5,1.7-0.6,7.5-0.6h6.9l1.2-1.1l1.2-1.1l0.1-7.1c0.1-4.1-0.5-6.5,0.2-7.6 - c0.8-1.3,3.1-0.9,7.6-0.9s6.3,0.1,6.8,0.5c0.7,0.5,0.7,1.1,0.8,7.3c0,3.7,0.2,7.1,0.3,7.6c0.1,0.5,0.7,1.2,1.4,1.6 - c1.1,0.7,1.9,0.7,7.8,0.7c3.8,0,6.9,0.2,7.3,0.4c0.7,0.4,0.7,0.9,0.7,7.3s0,7-0.7,7.3c-0.4,0.2-3.5,0.4-7.3,0.4 - c-5.8,0-6.7,0.1-7.8,0.7c-0.7,0.4-1.3,1.1-1.4,1.6s-0.2,3.9-0.3,7.6c-0.1,6.2-0.2,6.8-0.8,7.3c-0.6,0.4-2.2,0.5-6.8,0.5 - c-4.7,0-7.2,0.7-7.9-0.9c-0.5-1.2,0.1-3.8,0.1-7.5l-0.1-7.1l-1.2-1.1l-1.2-1.1h-6.9c-6.2,0-7-0.1-7.5-0.7c-0.5-0.6-0.6-1.7-0.6-7.1 - C868.7,78.2,868.8,77.5,869.4,76.9L869.4,76.9L869.4,76.9z"/> - <path id="path1514" class="st6" d="M274,123.2h5.3v10.5h-10.5v-10.5H274L274,123.2z"/> - <path id="path1518" class="st6" d="M238.8,122.6c2.3-14.8,12.3-27.2,26.2-32.4c20-7.5,43,2.3,51.4,22c2.6,6.1,3.5,8,3.5,15.7 - c0,6.5-0.6,7.9-1.6,11.3c-4.4,14.4-14.9,24.6-29.4,28.6c-2.9,0.8-5.2,1.1-10.7,1.1c-5.5,0-6.2-0.3-9-1.1 - c-17.7-4.8-29.9-20-30.5-38.1C238.5,127.1,238.6,124,238.8,122.6L238.8,122.6L238.8,122.6z M250.8,134.7 - c2.6,11.9,13,21.3,25.1,22.5c21.5,2.2,37.7-18.1,30.7-38.5c-1.4-4.2-3.3-7-7.1-10.8c-2.8-2.8-4.1-3.8-7.2-5.3 - c-5.1-2.6-8.7-3.3-14.6-3c-6.8,0.3-11.7,2.2-16.9,6.3C252.4,112.5,248.4,124,250.8,134.7L250.8,134.7L250.8,134.7z"/> - <path id="path1520" class="st7" d="M286.1,4.8c0.8-1.3,0.9-2.3,5.3-2.5c4.3-0.2,13.5,0.1,32.6,0.1c32.8,0,35.2-0.5,36.3,0.6 - c0.1,0.1,0.2,0.1,0.2,0.2c1.5,1.1,2.1,2.7,2.1,4.9v1.9h-77.3v-2C285.4,6.6,285.7,5.5,286.1,4.8L286.1,4.8L286.1,4.8z"/> - <path id="path1522" class="st7" d="M688.3,6.2c0.1-0.5,0.4-1.3,0.8-1.8c0.5-0.8,0.8-1.5,2.7-1.8c3.2-0.5,11.4-0.2,35.1-0.2 - c19.3,0,28.3-0.5,32.5-0.4c2.7,0.1,3.2,0.8,3.6,1c1.6,0.9,2.3,2.4,2.3,4.8v2.1h-77.3l0-1.4C688.1,7.7,688.2,6.7,688.3,6.2 - L688.3,6.2L688.3,6.2z"/> - <path id="path1524" class="st8" d="M874,334.6c20.6-10.1,37.2-21.9,53.9-38.2c35.7-34.8,57-78.7,63.1-129.7 - c0.6-5.1,0.8-13.1,1-40.9l0.2-34.6h1.4c1,0,3,0,3.7,1.4l1.1,2.1l-0.2,30.4c-0.1,16.9-0.2,33.5-0.5,37 - c-3.9,50.8-24.6,97.1-59.5,133.3c-16.9,17.6-35.7,31.5-57.9,42.8c-8.4,4.3-10.1,4.9-11.8,4.2c-1.1-0.4-4.2-2.7-4.1-3.2 - C864.4,339.2,869,337.1,874,334.6L874,334.6L874,334.6z"/> - <path id="path1530" class="st4" d="M96.7,3.4c0.5-0.6,1.2-1.7,5-1.6c6.7,0.3,27.2-0.2,79.1-0.1L263.4,2l6.5,2.6l15,7.9l39.4,0.1 - l39.6-0.4l13.2-7.1l7.7-2.9l7.3-0.4l48,0.1l22.1-0.1l34.1,0.1l10.5,0.1h10.5l8.6-0.1l25.6,0h19.4l27.5-0.2L620,1.8l11,0l19.6,0.1 - l10.2,0l5.7,0.5l5.8,1.9l5.5,3l9.8,5.3h39.5l39.6-0.4l9.8-5.3l5.3-2.7l5.1-1.8l7.6-0.7l13.3,0.2l12.2-0.1h11.1l10.9,0l9.9,0h6.5 - l2.9,0l17.4,0.1l21.5,0h10.8l8.4-0.1l5.1,0l5.9-0.2l3.1,2.2l1.1,3.2l-0.1,3.2c0,1.4-0.1,2.7-0.2,3.9c-0.1,0.9-0.1,1.8-0.4,2.6 - c-0.4,1-1,1.7-1.3,2c-0.2,0.2-1.7,1.2-4.8,1.3c-2.1,0.1-4.7,0-8.3,0c-4.1,0-9.7,0.1-16.4,0c-10-0.1-23.1,0-40.4-0.1 - c-13.2,0-28.7,0.2-47,0.1c-62.8-0.2-158-0.2-301-0.2L98.7,20l-1.8-1.3C94.3,16.9,94,6.1,96.7,3.4L96.7,3.4L96.7,3.4z"/> - <path id="path1532" class="st4" d="M535.4,22.4h392.4V31H143v-8.6H535.4L535.4,22.4z"/> - <ellipse id="path3879" class="st9" cx="279.4" cy="128.2" rx="40.3" ry="40.3"/> - <ellipse id="path3881" class="st10" cx="279.1" cy="128.2" rx="34.5" ry="34.1"/> - <path id="path96" d="M306.1,128.3l-26.4-26.8v7.7h-22.5v38.5h22.5v7.2L306.1,128.3L306.1,128.3z M265.2,136.1v-15.2h14.5v15.2 - H265.2z"/> -</g> -</svg> diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg deleted file mode 100644 index 53eef82c..00000000 --- a/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg +++ /dev/null @@ -1,84 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 996.25 690.92"> - <defs> - <style> - .cls-1 { - stroke: #fff; - stroke-miterlimit: 10; - stroke-width: 2px; - } - - .cls-1, .cls-2 { - fill: #444542; - } - - .cls-3 { - fill: #3b3b3b; - } - - .cls-3, .cls-4, .cls-2, .cls-5, .cls-6, .cls-7 { - stroke-width: 0px; - } - - .cls-4 { - fill: #3b3c3a; - } - - .cls-5 { - fill: #454644; - } - - .cls-6 { - fill: #20221f; - } - - .cls-7 { - fill: #121212; - } - </style> - </defs> - <g id="Front"> - <path id="Right_Grip" data-name="Right Grip" class="cls-6" d="m739.17,492.09c34,28.2,27.6,35.9,68.5,108.5,36.7,74.7,64.4,104.4,125.1,84.1h0c95.3-57.9,59.3-145.3,43.6-275.2-10-60.6-35.6-190.3-35.6-190.3l-201.6,272.9Z"/> - <path id="Left_Grip" data-name="Left Grip" class="cls-6" d="m55.47,219.19s-25.6,129.7-35.6,190.3c-15.7,129.9-51.7,217.2,43.6,275.1h0c60.8,20.3,88.4-9.4,125.1-84.1,40.9-72.7,34.5-80.3,68.5-108.5L55.47,219.19Z"/> - <path id="Right_Bumper" data-name="Right Bumper" class="cls-3" d="m649.47,19.99c10.1-4.3,39.7-22.5,58.7-19.7,59.5.9,166.7,17.7,172.6,81.2"/> - <path id="Left_Bumper" data-name="Left Bumper" class="cls-3" d="m115.57,81.49C121.47,18.09,228.57,1.29,288.17.29c19-2.8,48.6,15.4,58.7,19.7"/> - <path id="Background" class="cls-7" d="m739.17,492.09c35.5-30.8,68.5-74.7,96-113.5,26.9-36.3,94.7-136.7,105.6-159.3,0-2.4-6.3-30.1-12.8-56.2C892.27,3.49,675.37,19.69,498.17,19.69S104.07,3.49,68.27,162.99c-6.5,26-12.8,53.8-12.8,56.2,10.9,22.6,78.8,123,105.6,159.3,27.5,38.8,60.5,82.8,96,113.5"/> - <g id="Directional_Pad" data-name="Directional Pad"> - <path id="Background-2" data-name="Background" class="cls-2" d="m439.37,325.09h-40c-2.8,0-5-2.2-5-5v-40c0-2.8-2.2-5-5-5h-30c-2.8,0-5,2.2-5,5v40c0,2.8-2.2,5-5,5h-40c-2.8,0-5,2.2-5,5v30c0,2.8,2.2,5,5,5h40c2.8,0,5,2.2,5,5v40c0,2.8,2.2,5,5,5h30c2.8,0,5-2.2,5-5v-40c0-2.8,2.2-5,5-5h40c2.8,0,5-2.2,5-5v-30c0-2.7-2.2-5-5-5Z"/> - </g> - <g id="R_Thumbstick" data-name="R Thumbstick"> - <circle id="Background-3" data-name="Background" class="cls-6" cx="623.77" cy="345.09" r="55"/> - <circle id="Stick" class="cls-1" cx="623.77" cy="345.09" r="45"/> - </g> - <g id="L_Thumbstick" data-name="L Thumbstick"> - <circle id="Background-4" data-name="Background" class="cls-6" cx="213.37" cy="206.39" r="55"/> - <circle id="Stick-2" data-name="Stick" class="cls-1" cx="213.37" cy="206.39" r="45" transform="translate(-24.53 383.95) rotate(-80.78)"/> - </g> - <g id="Minus_Button" data-name="Minus Button"> - <circle id="_Background" data-name=" Background" class="cls-5" cx="374.17" cy="130.89" r="22.5"/> - </g> - <g id="Plus_Button" data-name="Plus Button"> - <circle id="_Background-2" data-name=" Background" class="cls-5" cx="623.57" cy="131.19" r="22.5"/> - </g> - <g id="Home_Button" data-name="Home Button"> - <circle id="_Background-3" data-name=" Background" class="cls-5" cx="578.57" cy="206.39" r="22.5"/> - </g> - <g id="Capture_Button" data-name="Capture Button"> - <path class="cls-5" d="m441.77,228.09h-30c-2.8,0-5-2.2-5-5v-29.5c0-2.8,2.2-5,5-5h30c2.8,0,5,2.2,5,5v29.5c0,2.7-2.2,5-5,5Z"/> - </g> - <g id="Buttons"> - <g id="A_Button" data-name="A Button"> - <circle id="Background-5" data-name="Background" class="cls-4" cx="837.07" cy="206.39" r="35"/> - </g> - <g id="X_Button" data-name="X Button"> - <circle id="Background-6" data-name="Background" class="cls-4" cx="767.07" cy="136.39" r="35"/> - </g> - <g id="Y_Button" data-name="Y Button"> - <circle id="Background-7" data-name="Background" class="cls-4" cx="697.07" cy="206.39" r="35"/> - </g> - <g id="B_Button" data-name="B Button"> - <circle id="Background-8" data-name="Background" class="cls-4" cx="767.07" cy="276.39" r="35"/> - </g> - </g> - </g> -</svg> \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/de_DE.json b/src/Ryujinx.Ava/Assets/Locales/de_DE.json deleted file mode 100644 index 7cdcdf5a..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/de_DE.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Deutsch", - "MenuBarFileOpenApplet": "Öffne Anwendung", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii-Editor-Applet im Standalone-Modus", - "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", - "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", - "SettingsTabSystemMemoryManagerModeSoftware": "Software", - "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", - "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", - "MenuBarFile": "_Datei", - "MenuBarFileOpenFromFile": "_Datei öffnen", - "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", - "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", - "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", - "MenuBarFileExit": "_Beenden", - "MenuBarOptions": "Optionen", - "MenuBarOptionsToggleFullscreen": "Vollbild", - "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", - "MenuBarOptionsStopEmulation": "Emulation beenden", - "MenuBarOptionsSettings": "_Einstellungen", - "MenuBarOptionsManageUserProfiles": "_Benutzerprofile verwalten", - "MenuBarActions": "_Aktionen", - "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", - "MenuBarActionsScanAmiibo": "Amiibo scannen", - "MenuBarTools": "_Werkzeuge", - "MenuBarToolsInstallFirmware": "Firmware installieren", - "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", - "MenuBarToolsManageFileTypes": "Dateitypen verwalten", - "MenuBarToolsInstallFileTypes": "Dateitypen installieren", - "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", - "MenuBarHelp": "Hilfe", - "MenuBarHelpCheckForUpdates": "Nach Updates suchen", - "MenuBarHelpAbout": "Über Ryujinx", - "MenuSearch": "Suchen...", - "GameListHeaderFavorite": "Favorit", - "GameListHeaderIcon": "Icon", - "GameListHeaderApplication": "Name", - "GameListHeaderDeveloper": "Entwickler", - "GameListHeaderVersion": "Version", - "GameListHeaderTimePlayed": "Spielzeit", - "GameListHeaderLastPlayed": "Zuletzt gespielt", - "GameListHeaderFileExtension": "Dateiformat", - "GameListHeaderFileSize": "Dateigröße", - "GameListHeaderPath": "Pfad", - "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", - "GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", - "GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT-Cache des Spiels beinhaltet", - "GameListContextMenuManageTitleUpdates": "Verwalte Spiel-Updates", - "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", - "GameListContextMenuManageDlc": "Verwalten von DLC", - "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", - "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", - "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", - "GameListContextMenuCacheManagement": "Cache Verwaltung", - "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", - "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader-Cache der Anwendung", - "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC-Verzeichnis öffnen", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC-Cache der Anwendung beinhaltet", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader-Cache-Verzeichnis öffnen", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", - "GameListContextMenuExtractData": "Daten extrahieren", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", - "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", - "StatusBarSystemVersion": "Systemversion: {0}", - "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", - "LinuxVmMaxMapCountDialogTextPrimary": "Möchtest Du den Wert von vm.max_map_count auf {0} erhöhen", - "LinuxVmMaxMapCountDialogTextSecondary": "Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ja, bis zum nächsten Neustart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Ja, permanent", - "LinuxVmMaxMapCountWarningTextPrimary": "Maximale Anzahl an Speicherzuordnungen ist niedriger als empfohlen.", - "LinuxVmMaxMapCountWarningTextSecondary": "Der aktuelle Wert von vm.max_map_count ({0}) ist kleiner als {1}. Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.\n\nDu kannst das Limit entweder manuell erhöhen oder pkexec installieren, damit Ryujinx Dir dabei hilft.", - "Settings": "Einstellungen", - "SettingsTabGeneral": "Oberfläche", - "SettingsTabGeneralGeneral": "Allgemein", - "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", - "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", - "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", - "SettingsTabGeneralHideCursorNever": "Niemals", - "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", - "SettingsTabGeneralHideCursorAlways": "Immer", - "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", - "SettingsTabGeneralAdd": "Hinzufügen", - "SettingsTabGeneralRemove": "Entfernen", - "SettingsTabSystem": "System", - "SettingsTabSystemCore": "Kern", - "SettingsTabSystemSystemRegion": "Systemregion:", - "SettingsTabSystemSystemRegionJapan": "Japan", - "SettingsTabSystemSystemRegionUSA": "USA", - "SettingsTabSystemSystemRegionEurope": "Europa", - "SettingsTabSystemSystemRegionAustralia": "Australien", - "SettingsTabSystemSystemRegionChina": "China", - "SettingsTabSystemSystemRegionKorea": "Korea", - "SettingsTabSystemSystemRegionTaiwan": "Taiwan", - "SettingsTabSystemSystemLanguage": "Systemsprache:", - "SettingsTabSystemSystemLanguageJapanese": "Japanisch", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", - "SettingsTabSystemSystemLanguageFrench": "Französisch", - "SettingsTabSystemSystemLanguageGerman": "Deutsch", - "SettingsTabSystemSystemLanguageItalian": "Italienisch", - "SettingsTabSystemSystemLanguageSpanish": "Spanisch", - "SettingsTabSystemSystemLanguageChinese": "Chinesisch", - "SettingsTabSystemSystemLanguageKorean": "Koreanisch", - "SettingsTabSystemSystemLanguageDutch": "Niederländisch", - "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", - "SettingsTabSystemSystemLanguageRussian": "Russisch", - "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", - "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", - "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", - "SettingsTabSystemSystemTimeZone": "System-Zeitzone:", - "SettingsTabSystemSystemTime": "Systemzeit:", - "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung", - "SettingsTabSystemAudioBackend": "Audio-Backend:", - "SettingsTabSystemAudioBackendDummy": "Ohne Funktion", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", - "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GiB", - "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", - "SettingsTabGraphics": "Grafik", - "SettingsTabGraphicsAPI": "Grafik-API", - "SettingsTabGraphicsEnableShaderCache": "Shader-Cache aktivieren", - "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", - "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", - "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "An Fenster anpassen", - "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", - "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader-Dump-Pfad:", - "SettingsTabLogging": "Logs", - "SettingsTabLoggingLogging": "Logs", - "SettingsTabLoggingEnableLoggingToFile": "Protokollierung in Datei aktivieren", - "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", - "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", - "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", - "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", - "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", - "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", - "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", - "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen", - "SettingsTabLoggingDeveloperOptionsNote": "ACHTUNG: Wird die Leistung reduzieren", - "SettingsTabLoggingGraphicsBackendLogLevel": "Protokollstufe des Grafik-Backends:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle", - "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", - "SettingsTabInput": "Eingabe", - "SettingsTabInputEnableDockedMode": "Angedockter Modus", - "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", - "SettingsButtonSave": "Speichern", - "SettingsButtonClose": "Schließen", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Abbrechen", - "SettingsButtonApply": "Übernehmen", - "ControllerSettingsPlayer": "Spieler", - "ControllerSettingsPlayer1": "Spieler 1", - "ControllerSettingsPlayer2": "Spieler 2", - "ControllerSettingsPlayer3": "Spieler 3", - "ControllerSettingsPlayer4": "Spieler 4", - "ControllerSettingsPlayer5": "Spieler 5", - "ControllerSettingsPlayer6": "Spieler 6", - "ControllerSettingsPlayer7": "Spieler 7", - "ControllerSettingsPlayer8": "Spieler 8", - "ControllerSettingsHandheld": "Handheld", - "ControllerSettingsInputDevice": "Eingabegerät", - "ControllerSettingsRefresh": "Aktualisieren", - "ControllerSettingsDeviceDisabled": "Deaktiviert", - "ControllerSettingsControllerType": "Controller-Typ", - "ControllerSettingsControllerTypeHandheld": "Handheld", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "Joy-Con-Paar", - "ControllerSettingsControllerTypeJoyConLeft": "Linker Joy-Con", - "ControllerSettingsControllerTypeJoyConRight": "Rechter Joy-Con", - "ControllerSettingsProfile": "Profil", - "ControllerSettingsProfileDefault": "Standard", - "ControllerSettingsLoad": "Laden", - "ControllerSettingsAdd": "Hinzufügen", - "ControllerSettingsRemove": "Entfernen", - "ControllerSettingsButtons": "Aktionstasten", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Steuerkreuz", - "ControllerSettingsDPadUp": "Hoch", - "ControllerSettingsDPadDown": "Runter", - "ControllerSettingsDPadLeft": "Links", - "ControllerSettingsDPadRight": "Rechts", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Hoch", - "ControllerSettingsStickDown": "Runter", - "ControllerSettingsStickLeft": "Links", - "ControllerSettingsStickRight": "Rechts", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "X-Achse invertieren", - "ControllerSettingsStickInvertYAxis": "Y-Achse invertieren", - "ControllerSettingsStickDeadzone": "Deadzone:", - "ControllerSettingsLStick": "Linker Analogstick", - "ControllerSettingsRStick": "Rechter Analogstick", - "ControllerSettingsTriggersLeft": "Linker Trigger", - "ControllerSettingsTriggersRight": "Rechter Trigger", - "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", - "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", - "ControllerSettingsTriggers": "Trigger", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", - "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", - "ControllerSettingsMisc": "Verschiedenes", - "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", - "ControllerSettingsMotion": "Bewegung", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", - "ControllerSettingsMotionControllerSlot": "Controller-Slot:", - "ControllerSettingsMotionMirrorInput": "Eingabe spiegeln", - "ControllerSettingsMotionRightJoyConSlot": "Rechter Joy-Con-Slot:", - "ControllerSettingsMotionServerHost": "Server Host:", - "ControllerSettingsMotionGyroSensitivity": "Gyro-Empfindlichkeit:", - "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", - "ControllerSettingsSave": "Speichern", - "ControllerSettingsClose": "Schließen", - "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", - "UserProfilesSaveProfileName": "Profilname speichern", - "UserProfilesChangeProfileImage": "Profilbild ändern", - "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", - "UserProfilesAddNewProfile": "Neues Profil", - "UserProfilesDelete": "Löschen", - "UserProfilesClose": "Schließen", - "ProfileNameSelectionWatermark": "Wähle einen Spitznamen", - "ProfileImageSelectionTitle": "Auswahl des Profilbildes", - "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", - "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", - "ProfileImageSelectionImportImage": "Bilddatei importieren", - "ProfileImageSelectionSelectAvatar": "Firmware-Avatar auswählen", - "InputDialogTitle": "Eingabe-Dialog", - "InputDialogOk": "OK", - "InputDialogCancel": "Abbrechen", - "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", - "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", - "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", - "AvatarChoose": "Bestätigen", - "AvatarSetBackgroundColor": "Hintergrundfarbe auswählen", - "AvatarClose": "Schließen", - "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", - "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", - "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", - "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", - "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", - "MenuBarFileToolsHideUi": "Oberfläche ausblenden", - "GameListContextMenuRunApplication": "Anwendung ausführen", - "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", - "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", - "SettingsTabGeneralTheme": "Design", - "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", - "SettingsTabGeneralThemeBaseStyle": "Farbschema", - "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", - "SettingsTabGeneralThemeBaseStyleLight": "Hell", - "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", - "ButtonBrowse": "Durchsuchen", - "ControllerSettingsConfigureGeneral": "Konfigurieren", - "ControllerSettingsRumble": "Vibration", - "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", - "ControllerSettingsRumbleWeakMultiplier": "Schwacher Vibrations-Multiplikator", - "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Sollen Speicherdaten für dieses Spiel erstellt werden?", - "DialogConfirmationTitle": "Ryujinx - Bestätigung", - "DialogUpdaterTitle": "Ryujinx - Updater", - "DialogErrorTitle": "Ryujinx - Fehler", - "DialogWarningTitle": "Ryujinx - Warnung", - "DialogExitTitle": "Ryujinx - Beenden", - "DialogErrorMessage": "Ein Fehler ist aufgetreten", - "DialogExitMessage": "Ryujinx wirklich schließen?", - "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", - "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", - "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Suchen der angegebenen Speicherdaten aufgetreten: {0}", - "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", - "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", - "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", - "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", - "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", - "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", - "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", - "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", - "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", - "DialogUpdaterDownloadingMessage": "Update wird heruntergeladen...", - "DialogUpdaterExtractionMessage": "Update wird entpackt...", - "DialogUpdaterRenamingMessage": "Update wird umbenannt...", - "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", - "DialogUpdaterCompleteMessage": "Update abgeschlossen!", - "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", - "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", - "DialogUpdaterArchNotSupportedSubMessage": "Nur 64-Bit-Systeme werden unterstützt!", - "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", - "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", - "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", - "DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/", - "DialogRestartRequiredMessage": "Neustart erforderlich", - "DialogThemeRestartMessage": "Das Design wurde gespeichert. Ein Neustart ist erforderlich, um das Design anzuwenden.", - "DialogThemeRestartSubMessage": "Jetzt neu starten?", - "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", - "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", - "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", - "DialogInstallFileTypesSuccessMessage": "Dateitypen erfolgreich installiert!", - "DialogInstallFileTypesErrorMessage": "Dateitypen konnten nicht installiert werden.", - "DialogUninstallFileTypesSuccessMessage": "Dateitypen erfolgreich deinstalliert!", - "DialogUninstallFileTypesErrorMessage": "Deinstallation der Dateitypen fehlgeschlagen.", - "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", - "DialogControllerAppletTitle": "Controller-Applet", - "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", - "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", - "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", - "DialogAmiiboApiTitle": "Amiibo-API", - "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", - "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", - "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", - "DialogProfileDeleteProfileTitle": "Profil löschen", - "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", - "DialogWarning": "Warnung", - "DialogPPTCDeletionMessage": "Du bist dabei den PPTC für das folgende Spiel als ungültig zu markieren:\n\n{0}\n\nWirklich fortfahren?", - "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", - "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", - "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", - "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", - "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", - "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", - "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", - "DialogUserProfileUnsavedChangesTitle": "Warnung - Nicht gespeicherte Änderungen", - "DialogUserProfileUnsavedChangesMessage": "Sie haben Änderungen an diesem Nutzerprofil vorgenommen, die nicht gespeichert wurden.", - "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", - "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", - "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", - "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", - "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", - "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", - "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", - "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", - "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", - "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", - "CommonAuto": "Auto", - "CommonOff": "Aus", - "CommonOn": "An", - "InputDialogYes": "Ja", - "InputDialogNo": "Nein", - "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", - "MenuBarOptionsPauseEmulation": "Pause", - "MenuBarOptionsResumeEmulation": "Fortsetzen", - "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", - "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", - "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", - "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", - "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", - "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", - "AboutRyujinxAboutTitle": "Über:", - "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", - "AboutRyujinxMaintainersTitle": "Entwickelt von:", - "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", - "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", - "AmiiboSeriesLabel": "Amiibo Serie", - "AmiiboCharacterLabel": "Charakter", - "AmiiboScanButtonLabel": "Einscannen", - "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", - "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", - "DlcManagerTableHeadingEnabledLabel": "Aktiviert", - "DlcManagerTableHeadingTitleIdLabel": "Title-ID", - "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", - "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", - "DlcManagerRemoveAllButton": "Entferne alle", - "DlcManagerEnableAllButton": "Alle aktivieren", - "DlcManagerDisableAllButton": "Alle deaktivieren", - "MenuBarOptionsChangeLanguage": "Sprache ändern", - "MenuBarShowFileTypes": "Dateitypen anzeigen", - "CommonSort": "Sortieren", - "CommonShowNames": "Spiel-Namen anzeigen", - "CommonFavorite": "Favoriten", - "OrderAscending": "Aufsteigend", - "OrderDescending": "Absteigend", - "SettingsTabGraphicsFeatures": "Erweiterungen", - "ErrorWindowTitle": "Fehler-Fenster", - "ToggleDiscordTooltip": "Zeige momentanes Spiel auf Discord", - "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", - "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", - "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", - "CustomThemeCheckTooltip": "Verwende ein eigenes Design für die Emulator-Benutzeroberfläche", - "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", - "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", - "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", - "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", - "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", - "RegionTooltip": "Ändert die Systemregion", - "LanguageTooltip": "Ändert die Systemsprache", - "TimezoneTooltip": "Ändert die Systemzeitzone", - "TimeTooltip": "Ändert die Systemzeit", - "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", - "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", - "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", - "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", - "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", - "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", - "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", - "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", - "UseHypervisorTooltip": "Verwende Hypervisor anstelle von JIT. Verbessert die Leistung stark, falls vorhanden, kann jedoch in seinem aktuellen Zustand instabil sein.", - "DRamTooltip": "Erhöht den Arbeitsspeicher des emulierten Systems von 4 GiB auf 6 GiB.\n\nDies ist nur für Texturenpakete mit höherer Auflösung oder Mods mit 4K-Auflösung nützlich. Diese Option verbessert NICHT die Leistung.\n\nIm Zweifelsfall AUS lassen.", - "IgnoreMissingServicesTooltip": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.", - "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", - "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", - "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", - "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", - "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", - "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", - "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", - "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", - "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", - "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", - "InfoLogTooltip": "Ausgabe von Info-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", - "WarnLogTooltip": "Ausgabe von Warn-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", - "ErrorLogTooltip": "Ausgabe von Fehler-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", - "TraceLogTooltip": "Ausgabe von Trace-Log in der Konsole. Hat keinen Einfluss auf die Leistung.", - "GuestLogTooltip": "Ausgabe von Gast-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", - "FileAccessLogTooltip": "Ausgabe von FS-Zugriff-Logs in der Konsole.", - "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", - "DeveloperOptionTooltip": "Mit Vorsicht verwenden", - "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", - "DebugLogTooltip": "Ausgabe von Debug-Logs in der Konsole.\n\nVerwende diese Option nur auf ausdrückliche Anweisung von Ryujinx Entwicklern, da sie das Lesen der Protokolle erschwert und die Leistung des Emulators verschlechtert.", - "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", - "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", - "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", - "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", - "ExitTooltip": "Beendet Ryujinx", - "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", - "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", - "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", - "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", - "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", - "GridSize": "Rastergröße", - "GridSizeTooltip": "Ändert die Größe der Rasterelemente", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", - "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", - "SettingsTabSystemAudioVolume": "Lautstärke: ", - "AudioVolumeTooltip": "Ändert die Lautstärke", - "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", - "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", - "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", - "GameListContextMenuManageCheat": "Cheats verwalten", - "ControllerSettingsStickRange": "Bereich:", - "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", - "DialogStopEmulationMessage": "Emulation wirklich beenden?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Audio", - "SettingsTabNetwork": "Netzwerk", - "SettingsTabNetworkConnection": "Netwerkverbindung", - "SettingsTabCpuCache": "CPU-Cache", - "SettingsTabCpuMemory": "CPU-Speicher", - "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", - "UpdaterDisabledWarningTitle": "Updater deaktiviert!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", - "ControllerSettingsRotate90": "Um 90° rotieren", - "IconSize": "Cover Größe", - "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", - "MenuBarOptionsShowConsole": "Zeige Konsole", - "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", - "UserErrorNoKeys": "Keys nicht gefunden", - "UserErrorNoFirmware": "Firmware nicht gefunden", - "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", - "UserErrorApplicationNotFound": "Anwendung nicht gefunden", - "UserErrorUnknown": "Unbekannter Fehler", - "UserErrorUndefined": "Undefinierter Fehler", - "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", - "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", - "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", - "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", - "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", - "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", - "NoUpdate": "Kein Update", - "TitleUpdateVersionLabel": "Version {0} - {1}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Bestätigung", - "FileDialogAllTypes": "Alle Typen", - "Never": "Niemals", - "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", - "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", - "SoftwareKeyboard": "Software-Tastatur", - "SoftwareKeyboardModeNumbersOnly": "Nur Zahlen", - "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", - "SoftwareKeyboardModeASCII": "Nur ASCII-Text", - "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", - "UpdaterRenaming": "Alte Dateien umbenennen...", - "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", - "UpdaterAddingFiles": "Neue Dateien hinzufügen...", - "UpdaterExtracting": "Update extrahieren...", - "UpdaterDownloading": "Update herunterladen...", - "Game": "Spiel", - "Docked": "Docked", - "Handheld": "Handheld", - "ConnectionError": "Verbindungsfehler.", - "AboutPageDeveloperListMore": "{0} und mehr...", - "ApiError": "API Fehler.", - "LoadingHeading": "{0} wird gestartet", - "CompilingPPTC": "PTC wird kompiliert", - "CompilingShaders": "Shader werden kompiliert", - "AllKeyboards": "Alle Tastaturen", - "OpenFileDialogTitle": "Wähle eine unterstützte Datei", - "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", - "AllSupportedFormats": "Alle unterstützten Formate", - "RyujinxUpdater": "Ryujinx - Updater", - "SettingsTabHotkeys": "Tastatur Hotkeys", - "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", - "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", - "SettingsTabHotkeysPauseHotkey": "Pausieren:", - "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", - "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", - "ControllerRumbleTitle": "Vibration - Einstellungen", - "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche", - "SettingsXamlThemeFile": "Xaml Design-Datei", - "AvatarWindowTitle": "Profile verwalten - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Unbekannt", - "Usage": "Nutzung", - "Writable": "Beschreibbar", - "SelectDlcDialogTitle": "DLC-Dateien auswählen", - "SelectUpdateDialogTitle": "Update-Datei auswählen", - "UserProfileWindowTitle": "Benutzerprofile verwalten", - "CheatWindowTitle": "Spiel-Cheats verwalten", - "DlcWindowTitle": "Spiel-DLC verwalten", - "UpdateWindowTitle": "Spiel-Updates verwalten", - "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", - "UserProfilesEditProfile": "Profil bearbeiten", - "Cancel": "Abbrechen", - "Save": "Speichern", - "Discard": "Verwerfen", - "UserProfilesSetProfileImage": "Profilbild einrichten", - "UserProfileEmptyNameError": "Name ist erforderlich", - "UserProfileNoImageError": "Bitte ein Profilbild auswählen", - "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", - "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", - "UserProfilesName": "Name:", - "UserProfilesUserId": "Benutzer Id:", - "SettingsTabGraphicsBackend": "Grafik-Backend:", - "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", - "SettingsEnableTextureRecompression": "Textur-Rekompression", - "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", - "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", - "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", - "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", - "SettingsGpuBackendRestartMessage": "Das Grafik-Backend oder die Grafikkarteneinstellungen wurden geändert. Ein Neustart ist erforderlich um diese Einstellungen anzuwenden.", - "SettingsGpuBackendRestartSubMessage": "Ryujinx jetzt neu starten?", - "RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?", - "SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:", - "SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:", - "SettingsEnableMacroHLE": "HLE Makros aktivieren", - "SettingsEnableMacroHLETooltip": "High-Level-Emulation von GPU-Makrocode.\n\nVerbessert die Leistung, kann aber in einigen Spielen zu Grafikfehlern führen.\n\nBei Unsicherheit AKTIVIEREN.", - "SettingsEnableColorSpacePassthrough": "Farbraum Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Weist das Vulkan-Backend an, Farbinformationen ohne Angabe eines Farbraums weiterzuleiten. Für Benutzer mit Wide-Gamut-Displays kann dies zu lebendigeren Farben führen, allerdings auf Kosten der Farbkorrektheit.", - "VolumeShort": "Vol", - "UserProfilesManageSaves": "Speicherstände verwalten", - "DeleteUserSave": "Möchtest du den Spielerstand für dieses Spiel löschen?", - "IrreversibleActionNote": "Diese Option kann nicht rückgängig gemacht werden.", - "SaveManagerHeading": "Spielstände für {0} verwalten", - "SaveManagerTitle": "Speicherdaten Manager", - "Name": "Name", - "Size": "Größe", - "Search": "Suche", - "UserProfilesRecoverLostAccounts": "Konto wiederherstellen", - "Recover": "Wiederherstellen", - "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", - "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", - "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Spiel-Rendering an", - "GraphicsAALabel": "Antialiasing:", - "GraphicsScalingFilterLabel": "Skalierungsfilter:", - "GraphicsScalingFilterTooltip": "Ermöglicht Framebuffer-Skalierung", - "GraphicsScalingFilterLevelLabel": "Stufe", - "GraphicsScalingFilterLevelTooltip": "Skalierungsfilter-Stufe festlegen", - "SmaaLow": "SMAA Niedrig", - "SmaaMedium": "SMAA Mittel", - "SmaaHigh": "SMAA Hoch", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Nutzer bearbeiten", - "UserEditorTitleCreate": "Nutzer erstellen", - "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", - "NetworkInterfaceTooltip": "Die Netzwerkschnittstelle, die für LAN-Funktionen verwendet wird", - "NetworkInterfaceDefault": "Standard", - "PackagingShaders": "Verpackt Shader", - "AboutChangelogButton": "Changelog in GitHub öffnen", - "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/el_GR.json b/src/Ryujinx.Ava/Assets/Locales/el_GR.json deleted file mode 100644 index 59f50ad9..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/el_GR.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Ελληνικά", - "MenuBarFileOpenApplet": "Άνοιγμα Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", - "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", - "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", - "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", - "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", - "SettingsTabSystemUseHypervisor": "Χρήση Hypervisor", - "MenuBarFile": "_Αρχείο", - "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", - "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", - "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", - "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", - "MenuBarFileExit": "_Έξοδος", - "MenuBarOptions": "Επιλογές", - "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", - "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", - "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", - "MenuBarOptionsSettings": "_Ρυθμίσεις", - "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", - "MenuBarActions": "_Δράσεις", - "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", - "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", - "MenuBarTools": "_Εργαλεία", - "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", - "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", - "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", - "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", - "MenuBarHelp": "Βοήθεια", - "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", - "MenuBarHelpAbout": "Σχετικά με", - "MenuSearch": "Αναζήτηση...", - "GameListHeaderFavorite": "Αγαπημένο", - "GameListHeaderIcon": "Εικονίδιο", - "GameListHeaderApplication": "Όνομα", - "GameListHeaderDeveloper": "Προγραμματιστής", - "GameListHeaderVersion": "Έκδοση", - "GameListHeaderTimePlayed": "Χρόνος", - "GameListHeaderLastPlayed": "Παίχτηκε", - "GameListHeaderFileExtension": "Κατάληξη", - "GameListHeaderFileSize": "Μέγεθος Αρχείου", - "GameListHeaderPath": "Τοποθεσία", - "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", - "GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", - "GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", - "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", - "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", - "GameListContextMenuManageDlc": "Διαχείριση DLC", - "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", - "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", - "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", - "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", - "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", - "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", - "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", - "GameListContextMenuExtractDataLogo": "Λογότυπο", - "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", - "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", - "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", - "Settings": "Ρυθμίσεις", - "SettingsTabGeneral": "Εμφάνιση", - "SettingsTabGeneralGeneral": "Γενικά", - "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", - "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", - "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", - "SettingsTabGeneralHideCursorNever": "Ποτέ", - "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", - "SettingsTabGeneralHideCursorAlways": "Πάντα", - "SettingsTabGeneralGameDirectories": "Τοποθεσίες παιχνιδιών", - "SettingsTabGeneralAdd": "Προσθήκη", - "SettingsTabGeneralRemove": "Αφαίρεση", - "SettingsTabSystem": "Σύστημα", - "SettingsTabSystemCore": "Πυρήνας", - "SettingsTabSystemSystemRegion": "Περιοχή Συστήματος:", - "SettingsTabSystemSystemRegionJapan": "Ιαπωνία", - "SettingsTabSystemSystemRegionUSA": "ΗΠΑ", - "SettingsTabSystemSystemRegionEurope": "Ευρώπη", - "SettingsTabSystemSystemRegionAustralia": "Αυστραλία", - "SettingsTabSystemSystemRegionChina": "Κίνα", - "SettingsTabSystemSystemRegionKorea": "Κορέα", - "SettingsTabSystemSystemRegionTaiwan": "Ταϊβάν", - "SettingsTabSystemSystemLanguage": "Γλώσσα Συστήματος:", - "SettingsTabSystemSystemLanguageJapanese": "Ιαπωνικά", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Αμερικάνικα Αγγλικά", - "SettingsTabSystemSystemLanguageFrench": "Γαλλικά", - "SettingsTabSystemSystemLanguageGerman": "Γερμανικά", - "SettingsTabSystemSystemLanguageItalian": "Ιταλικά", - "SettingsTabSystemSystemLanguageSpanish": "Ισπανικά", - "SettingsTabSystemSystemLanguageChinese": "Κινέζικα", - "SettingsTabSystemSystemLanguageKorean": "Κορεάτικα", - "SettingsTabSystemSystemLanguageDutch": "Ολλανδικά", - "SettingsTabSystemSystemLanguagePortuguese": "Πορτογαλικά", - "SettingsTabSystemSystemLanguageRussian": "Ρώσικα", - "SettingsTabSystemSystemLanguageTaiwanese": "Ταϊβανέζικα", - "SettingsTabSystemSystemLanguageBritishEnglish": "Βρετανικά Αγγλικά", - "SettingsTabSystemSystemLanguageCanadianFrench": "Καναδικά Γαλλικά", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Λατινοαμερικάνικα Ισπανικά", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Απλοποιημένα Κινέζικα", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Παραδοσιακά Κινεζικά", - "SettingsTabSystemSystemTimeZone": "Ζώνη Ώρας Συστήματος:", - "SettingsTabSystemSystemTime": "Ώρα Συστήματος:", - "SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", - "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", - "SettingsTabSystemAudioBackend": "Backend Ήχου:", - "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Μικροδιορθώσεις", - "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", - "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GiB", - "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", - "SettingsTabGraphics": "Γραφικά", - "SettingsTabGraphicsAPI": "API Γραφικά", - "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", - "SettingsTabGraphicsAnisotropicFiltering": "Ανισότροπο Φιλτράρισμα:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Αυτόματο", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Κλίμακα Ανάλυσης:", - "SettingsTabGraphicsResolutionScaleCustom": "Προσαρμοσμένο (Δεν συνιστάται)", - "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Έκταση σε όλο το παράθυρο", - "SettingsTabGraphicsDeveloperOptions": "Επιλογές Προγραμματιστή", - "SettingsTabGraphicsShaderDumpPath": "Τοποθεσία Shaders Γραφικών:", - "SettingsTabLogging": "Καταγραφή", - "SettingsTabLoggingLogging": "Καταγραφή", - "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", - "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", - "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", - "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", - "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", - "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", - "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", - "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", - "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", - "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", - "SettingsTabLoggingDeveloperOptionsNote": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Θα μειώσει την απόδοση", - "SettingsTabLoggingGraphicsBackendLogLevel": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα", - "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", - "SettingsTabInput": "Χειρισμός", - "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", - "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", - "SettingsButtonSave": "Αποθήκευση", - "SettingsButtonClose": "Κλείσιμο", - "SettingsButtonOk": "ΟΚ", - "SettingsButtonCancel": "Ακύρωση", - "SettingsButtonApply": "Εφαρμογή", - "ControllerSettingsPlayer": "Παίχτης", - "ControllerSettingsPlayer1": "Παίχτης 1", - "ControllerSettingsPlayer2": "Παίχτης 2", - "ControllerSettingsPlayer3": "Παίχτης 3", - "ControllerSettingsPlayer4": "Παίχτης 4", - "ControllerSettingsPlayer5": "Παίχτης 5", - "ControllerSettingsPlayer6": "Παίχτης 6", - "ControllerSettingsPlayer7": "Παίχτης 7", - "ControllerSettingsPlayer8": "Παίχτης 8", - "ControllerSettingsHandheld": "Χειροκίνητο", - "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", - "ControllerSettingsRefresh": "Ανανέωση", - "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", - "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", - "ControllerSettingsControllerTypeHandheld": "Φορητό", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", - "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", - "ControllerSettingsProfile": "Προφίλ", - "ControllerSettingsProfileDefault": "Προκαθορισμένο", - "ControllerSettingsLoad": "Φόρτωση", - "ControllerSettingsAdd": "Προσθήκη", - "ControllerSettingsRemove": "Αφαίρεση", - "ControllerSettingsButtons": "Κουμπιά", - "ControllerSettingsButtonA": "Α", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Κατευθυντικό Pad", - "ControllerSettingsDPadUp": "Πάνω", - "ControllerSettingsDPadDown": "Κάτω", - "ControllerSettingsDPadLeft": "Αριστερά", - "ControllerSettingsDPadRight": "Δεξιά", - "ControllerSettingsStickButton": "Κουμπί", - "ControllerSettingsStickUp": "Πάνω", - "ControllerSettingsStickDown": "Κάτω", - "ControllerSettingsStickLeft": "Αριστερά", - "ControllerSettingsStickRight": "Δεξιά", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", - "ControllerSettingsLStick": "Αριστερός Μοχλός", - "ControllerSettingsRStick": "Δεξιός Μοχλός", - "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", - "ControllerSettingsTriggersRight": "Δεξιά Σκανδάλη", - "ControllerSettingsTriggersButtonsLeft": "Αριστερά Κουμπιά Σκανδάλης", - "ControllerSettingsTriggersButtonsRight": "Δεξιά Κουμπιά Σκανδάλης", - "ControllerSettingsTriggers": "Σκανδάλες", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Αριστερά Κουμπιά", - "ControllerSettingsExtraButtonsRight": "Δεξιά Κουμπιά", - "ControllerSettingsMisc": "Διάφορα", - "ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:", - "ControllerSettingsMotion": "Κίνηση", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", - "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", - "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", - "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", - "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", - "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", - "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", - "ControllerSettingsSave": "Αποθήκευση", - "ControllerSettingsClose": "Κλείσιμο", - "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", - "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", - "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", - "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", - "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", - "UserProfilesDelete": "Διαγράφω", - "UserProfilesClose": "Κλείσιμο", - "ProfileNameSelectionWatermark": "Επιλέξτε ψευδώνυμο", - "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", - "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", - "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", - "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", - "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", - "InputDialogTitle": "Διάλογος Εισαγωγής", - "InputDialogOk": "ΟΚ", - "InputDialogCancel": "Ακύρωση", - "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", - "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", - "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", - "AvatarChoose": "Επιλογή", - "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", - "AvatarClose": "Κλείσιμο", - "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", - "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", - "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", - "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", - "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", - "MenuBarFileToolsHideUi": "Απόκρυψη UI", - "GameListContextMenuRunApplication": "Run Application", - "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", - "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", - "SettingsTabGeneralTheme": "Θέμα", - "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", - "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", - "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", - "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", - "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", - "ButtonBrowse": "Αναζήτηση", - "ControllerSettingsConfigureGeneral": "Παραμέτρων", - "ControllerSettingsRumble": "Δόνηση", - "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", - "ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης", - "DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Θέλετε να αποθηκεύσετε δεδομένα για αυτό το παιχνίδι;", - "DialogConfirmationTitle": "Ryujinx - Επιβεβαίωση", - "DialogUpdaterTitle": "Ryujinx - Ενημερωτής", - "DialogErrorTitle": "Ryujinx - Σφάλμα", - "DialogWarningTitle": "Ryujinx - Προειδοποίηση", - "DialogExitTitle": "Ryujinx - Έξοδος", - "DialogErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", - "DialogExitMessage": "Είστε βέβαιοι ότι θέλετε να κλείσετε το Ryujinx;", - "DialogExitSubMessage": "Όλα τα μη αποθηκευμένα δεδομένα θα χαθούν!", - "DialogMessageCreateSaveErrorMessage": "Σφάλμα κατά τη δημιουργία των αποθηκευμένων δεδομένων: {0}", - "DialogMessageFindSaveErrorMessage": "Σφάλμα κατά την εύρεση των αποθηκευμένων δεδομένων: {0}", - "FolderDialogExtractTitle": "Επιλέξτε τον φάκελο στον οποίο θέλετε να εξαγάγετε", - "DialogNcaExtractionMessage": "Εξαγωγή ενότητας {0} από {1}...", - "DialogNcaExtractionTitle": "Ryujinx - NCA Εξαγωγέας Τμημάτων", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Αποτυχία εξαγωγής. Η κύρια NCA δεν υπήρχε στο επιλεγμένο αρχείο.", - "DialogNcaExtractionCheckLogErrorMessage": "Αποτυχία εξαγωγής. Διαβάστε το αρχείο καταγραφής για περισσότερες πληροφορίες.", - "DialogNcaExtractionSuccessMessage": "Η εξαγωγή ολοκληρώθηκε με επιτυχία.", - "DialogUpdaterConvertFailedMessage": "Αποτυχία μετατροπής της τρέχουσας έκδοσης Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Ακύρωση Ενημέρωσης!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Προέκυψε ένα σφάλμα στη λήψη πληροφοριών έκδοσης από τα GitHub Releases. Αυτό δύναται να συμβεί αν μία έκδοση χτίζεται αυτή τη στιγμή στα GitHub Actions. Παρακαλούμε προσπαθήστε αργότερα.", - "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", - "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", - "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", - "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", - "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", - "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", - "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", - "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", - "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", - "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", - "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", - "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", - "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", - "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", - "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", - "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", - "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", - "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", - "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", - "DialogInstallFileTypesErrorMessage": "Απέτυχε η εγκατάσταση τύπων αρχείων.", - "DialogUninstallFileTypesSuccessMessage": "Επιτυχής απεγκατάσταση τύπων αρχείων!", - "DialogUninstallFileTypesErrorMessage": "Αποτυχία απεγκατάστασης τύπων αρχείων.", - "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", - "DialogControllerAppletTitle": "Applet Χειρισμού", - "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", - "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", - "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", - "DialogAmiiboApiTitle": "API για Amiibo.", - "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", - "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", - "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", - "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", - "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", - "DialogWarning": "Προειδοποίηση", - "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", - "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", - "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", - "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", - "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", - "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", - "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", - "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", - "DialogUserProfileUnsavedChangesTitle": "Προσοχή - Μην Αποθηκευμένες Αλλαγές.", - "DialogUserProfileUnsavedChangesMessage": "Έχετε κάνει αλλαγές σε αυτό το προφίλ χρήστη που δεν έχουν αποθηκευτεί.", - "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", - "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", - "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", - "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", - "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", - "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", - "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", - "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", - "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", - "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", - "CommonAuto": "Αυτόματο", - "CommonOff": "Ανενεργό", - "CommonOn": "Ενεργό", - "InputDialogYes": "Ναι", - "InputDialogNo": "Όχι", - "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", - "MenuBarOptionsPauseEmulation": "Παύση", - "MenuBarOptionsResumeEmulation": "Συνέχιση", - "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", - "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", - "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutRyujinxAboutTitle": "Σχετικά με:", - "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", - "AboutRyujinxMaintainersTitle": "Συντηρείται από:", - "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", - "AmiiboSeriesLabel": "Σειρά Amiibo", - "AmiiboCharacterLabel": "Χαρακτήρας", - "AmiiboScanButtonLabel": "Σαρώστε το", - "AmiiboOptionsShowAllLabel": "Εμφάνιση όλων των Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", - "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιημένο", - "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", - "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", - "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", - "DlcManagerRemoveAllButton": "Αφαίρεση όλων", - "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", - "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", - "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", - "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", - "CommonSort": "Κατάταξη", - "CommonShowNames": "Εμφάνιση ονομάτων", - "CommonFavorite": "Αγαπημένα", - "OrderAscending": "Αύξουσα", - "OrderDescending": "Φθίνουσα", - "SettingsTabGraphicsFeatures": "Χαρακτηριστικά & Βελτιώσεις", - "ErrorWindowTitle": "Παράθυρο σφάλματος", - "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", - "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", - "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", - "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", - "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", - "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", - "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", - "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", - "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", - "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", - "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", - "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", - "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", - "TimeTooltip": "Αλλαγή Ώρας Συστήματος", - "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", - "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", - "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", - "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", - "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", - "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", - "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", - "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", - "UseHypervisorTooltip": "Χρησιμοποιήστε Hypervisor αντί για JIT. Βελτιώνει σημαντικά την απόδοση όταν διατίθεται, αλλά μπορεί να είναι ασταθής στην τρέχουσα κατάστασή του.", - "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB", - "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", - "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", - "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", - "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", - "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", - "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", - "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", - "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", - "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", - "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", - "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", - "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", - "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", - "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", - "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", - "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", - "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", - "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", - "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", - "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", - "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", - "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", - "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", - "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", - "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", - "ExitTooltip": "Έξοδος από το Ryujinx", - "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", - "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", - "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", - "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", - "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", - "GridSize": "Μέγεθος Πλέγματος", - "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", - "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", - "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", - "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", - "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", - "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", - "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", - "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", - "ControllerSettingsStickRange": "Εύρος:", - "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", - "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", - "SettingsTabCpu": "Επεξεργαστής", - "SettingsTabAudio": "Ήχος", - "SettingsTabNetwork": "Δίκτυο", - "SettingsTabNetworkConnection": "Σύνδεση δικτύου", - "SettingsTabCpuCache": "Προσωρινή Μνήμη CPU", - "SettingsTabCpuMemory": "Μνήμη CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", - "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", - "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ανοίγει τον εναλλακτικό SD card Atmosphere κατάλογο που περιέχει Mods για το Application. Χρήσιμο για mods τα οποία συσκευάστηκαν για την πραγματική κονσόλα.", - "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", - "IconSize": "Μέγεθος Εικονιδίου", - "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", - "MenuBarOptionsShowConsole": "Εμφάνιση Κονσόλας", - "ShaderCachePurgeError": "Σφάλμα κατά την εκκαθάριση του shader cache στο {0}: {1}", - "UserErrorNoKeys": "Τα κλειδιά δεν βρέθηκαν", - "UserErrorNoFirmware": "Το firmware δε βρέθηκε", - "UserErrorFirmwareParsingFailed": "Σφάλμα ανάλυσης firmware", - "UserErrorApplicationNotFound": "Η εφαρμογή δε βρέθηκε", - "UserErrorUnknown": "Άγνωστο σφάλμα", - "UserErrorUndefined": "Αόριστο σφάλμα", - "UserErrorNoKeysDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο 'prod.keys'", - "UserErrorNoFirmwareDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware", - "UserErrorFirmwareParsingFailedDescription": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.", - "UserErrorApplicationNotFoundDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει έγκυρη εφαρμογή στη συγκεκριμένη διαδρομή.", - "UserErrorUnknownDescription": "Παρουσιάστηκε άγνωστο σφάλμα.", - "UserErrorUndefinedDescription": "Παρουσιάστηκε ένα άγνωστο σφάλμα! Αυτό δεν πρέπει να συμβεί, παρακαλώ επικοινωνήστε με έναν προγραμματιστή!", - "OpenSetupGuideMessage": "Ανοίξτε τον Οδηγό Εγκατάστασης.", - "NoUpdate": "Καμία Eνημέρωση", - "TitleUpdateVersionLabel": "Version {0} - {1}", - "RyujinxInfo": "Ryujinx - Πληροφορίες", - "RyujinxConfirm": "Ryujinx - Επιβεβαίωση", - "FileDialogAllTypes": "Όλοι οι τύποι", - "Never": "Ποτέ", - "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", - "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", - "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Η εφαρμογή ζητά {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletMessage": "Η εφαρμογή ζητά ακριβώς {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις Ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletDockModeSet": "Η κατάσταση Docked ενεργοποιήθηκε. Το συσκευή παλάμης είναι επίσης μην αποδεκτό.", - "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", - "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", - "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", - "UpdaterExtracting": "Εξαγωγή Ενημέρωσης...", - "UpdaterDownloading": "Λήψη Ενημέρωσης...", - "Game": "Παιχνίδι", - "Docked": "Προσκολλημένο", - "Handheld": "Χειροκίνητο", - "ConnectionError": "Σφάλμα Σύνδεσης.", - "AboutPageDeveloperListMore": "{0} και περισσότερα...", - "ApiError": "Σφάλμα API.", - "LoadingHeading": "Φόρτωση {0}", - "CompilingPPTC": "Μεταγλώττιση του PTC", - "CompilingShaders": "Σύνταξη των Shaders", - "AllKeyboards": "Όλα τα πληκτρολόγια", - "OpenFileDialogTitle": "Επιλέξτε ένα υποστηριζόμενο αρχείο για άνοιγμα", - "OpenFolderDialogTitle": "Επιλέξτε ένα φάκελο με ένα αποσυμπιεσμένο παιχνίδι", - "AllSupportedFormats": "Όλες Οι Υποστηριζόμενες Μορφές", - "RyujinxUpdater": "Ryujinx Ενημερωτής", - "SettingsTabHotkeys": "Συντομεύσεις Πληκτρολογίου", - "SettingsTabHotkeysHotkeys": "Συντομεύσεις Πληκτρολογίου", - "SettingsTabHotkeysToggleVsyncHotkey": "Εναλλαγή VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Στιγμιότυπο Οθόνης:", - "SettingsTabHotkeysShowUiHotkey": "Εμφάνιση Διεπαφής Χρήστη:", - "SettingsTabHotkeysPauseHotkey": "Παύση:", - "SettingsTabHotkeysToggleMuteHotkey": "Σίγαση:", - "ControllerMotionTitle": "Ρυθμίσεις Ελέγχου Κίνησης", - "ControllerRumbleTitle": "Ρυθμίσεις Δόνησης", - "SettingsSelectThemeFileDialogTitle": "Επιλογή Αρχείου Θέματος", - "SettingsXamlThemeFile": "Αρχείο Θέματος Xaml", - "AvatarWindowTitle": "Διαχείριση Λογαριασμών - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Άγνωστο", - "Usage": "Χρήση", - "Writable": "Εγγράψιμο", - "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", - "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", - "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", - "CheatWindowTitle": "Διαχειριστής των Cheats", - "DlcWindowTitle": "Downloadable Content Manager", - "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", - "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", - "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", - "Cancel": "Ακύρωση", - "Save": "Αποθήκευση", - "Discard": "Απόρριψη", - "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", - "UserProfileEmptyNameError": "Απαιτείται όνομα", - "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", - "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Αύξηση της ανάλυσης:", - "SettingsTabHotkeysResScaleDownHotkey": "Μείωση της ανάλυσης:", - "UserProfilesName": "Όνομα:", - "UserProfilesUserId": "User Id:", - "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", - "SettingsTabGraphicsBackendTooltip": "Backend Γραφικών που θα χρησιμοποιηθεί", - "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", - "SettingsEnableTextureRecompressionTooltip": "Συμπιέζει συγκεκριμένα texture για να μειωθεί η χρήση της VRAM.\nΣυνίσταται για χρήση σε κάρτες γραφικών με λιγότερο από 4 GiB VRAM.\nΑφήστε το Απενεργοποιημένο αν είστε αβέβαιοι.", - "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", - "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", - "SettingsGpuBackendRestartMessage": "Οι ρυθμίσεις GPU έχουν αλλαχτεί. Θα χρειαστεί επανεκκίνηση του Ryujinx για να τεθούν σε ισχύ.", - "SettingsGpuBackendRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση τώρα;", - "RyujinxUpdaterMessage": "Θέλετε να ενημερώσετε το Ryujinx στην πιο πρόσφατη έκδοση:", - "SettingsTabHotkeysVolumeUpHotkey": "Αύξηση Έντασης:", - "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", - "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", - "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Έντ.", - "UserProfilesManageSaves": "Διαχείριση Των Save", - "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", - "IrreversibleActionNote": "Αυτή η ενέργεια είναι μη αναστρέψιμη.", - "SaveManagerHeading": "Manage Saves for {0}", - "SaveManagerTitle": "Διαχειριστής Save", - "Name": "Όνομα", - "Size": "Μέγεθος", - "Search": "Αναζήτηση", - "UserProfilesRecoverLostAccounts": "Ανάκτηση Χαμένων Λογαριασμών", - "Recover": "Ανάκτηση", - "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", - "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", - "GraphicsAATooltip": "Εφαρμόζει anti-aliasing στην απόδοση του παιχνιδιού", - "GraphicsAALabel": "Anti-Aliasing", - "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", - "GraphicsScalingFilterTooltip": "Ενεργοποιεί Κλίμακα Framebuffer", - "GraphicsScalingFilterLevelLabel": "Επίπεδο", - "GraphicsScalingFilterLevelTooltip": "Ορισμός Επιπέδου Φίλτρου Κλιμάκωσης", - "SmaaLow": "Χαμηλό SMAA", - "SmaaMedium": " Μεσαίο SMAA", - "SmaaHigh": "Υψηλό SMAA", - "SmaaUltra": "Oύλτρα SMAA", - "UserEditorTitle": "Επεξεργασία Χρήστη", - "UserEditorTitleCreate": "Δημιουργία Χρήστη", - "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", - "NetworkInterfaceTooltip": "Η διεπαφή δικτύου που χρησιμοποιείται για τα χαρακτηριστικά LAN", - "NetworkInterfaceDefault": "Προεπιλογή", - "PackagingShaders": "Shaders Συσκευασίας", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json deleted file mode 100644 index 2febf90e..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ /dev/null @@ -1,668 +0,0 @@ -{ - "Language": "English (US)", - "MenuBarFileOpenApplet": "Open Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", - "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", - "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", - "SettingsTabSystemMemoryManagerModeSoftware": "Software", - "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", - "SettingsTabSystemUseHypervisor": "Use Hypervisor", - "MenuBarFile": "_File", - "MenuBarFileOpenFromFile": "_Load Application From File", - "MenuBarFileOpenUnpacked": "Load _Unpacked Game", - "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", - "MenuBarFileOpenLogsFolder": "Open Logs Folder", - "MenuBarFileExit": "_Exit", - "MenuBarOptions": "_Options", - "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", - "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", - "MenuBarOptionsStopEmulation": "Stop Emulation", - "MenuBarOptionsSettings": "_Settings", - "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", - "MenuBarActions": "_Actions", - "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", - "MenuBarActionsScanAmiibo": "Scan An Amiibo", - "MenuBarTools": "_Tools", - "MenuBarToolsInstallFirmware": "Install Firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", - "MenuBarToolsManageFileTypes": "Manage file types", - "MenuBarToolsInstallFileTypes": "Install file types", - "MenuBarToolsUninstallFileTypes": "Uninstall file types", - "MenuBarHelp": "_Help", - "MenuBarHelpCheckForUpdates": "Check for Updates", - "MenuBarHelpAbout": "About", - "MenuSearch": "Search...", - "GameListHeaderFavorite": "Fav", - "GameListHeaderIcon": "Icon", - "GameListHeaderApplication": "Name", - "GameListHeaderDeveloper": "Developer", - "GameListHeaderVersion": "Version", - "GameListHeaderTimePlayed": "Play Time", - "GameListHeaderLastPlayed": "Last Played", - "GameListHeaderFileExtension": "File Ext", - "GameListHeaderFileSize": "File Size", - "GameListHeaderPath": "Path", - "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", - "GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save", - "GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", - "GameListContextMenuManageTitleUpdates": "Manage Title Updates", - "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", - "GameListContextMenuManageDlc": "Manage DLC", - "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", - "GameListContextMenuCacheManagement": "Cache Management", - "GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch", - "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", - "GameListContextMenuExtractData": "Extract Data", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", - "GameListContextMenuCreateShortcut": "Create Application Shortcut", - "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", - "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", - "StatusBarGamesLoaded": "{0}/{1} Games Loaded", - "StatusBarSystemVersion": "System Version: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", - "Settings": "Settings", - "SettingsTabGeneral": "User Interface", - "SettingsTabGeneralGeneral": "General", - "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", - "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", - "SettingsTabGeneralHideCursor": "Hide Cursor:", - "SettingsTabGeneralHideCursorNever": "Never", - "SettingsTabGeneralHideCursorOnIdle": "On Idle", - "SettingsTabGeneralHideCursorAlways": "Always", - "SettingsTabGeneralGameDirectories": "Game Directories", - "SettingsTabGeneralAdd": "Add", - "SettingsTabGeneralRemove": "Remove", - "SettingsTabSystem": "System", - "SettingsTabSystemCore": "Core", - "SettingsTabSystemSystemRegion": "System Region:", - "SettingsTabSystemSystemRegionJapan": "Japan", - "SettingsTabSystemSystemRegionUSA": "USA", - "SettingsTabSystemSystemRegionEurope": "Europe", - "SettingsTabSystemSystemRegionAustralia": "Australia", - "SettingsTabSystemSystemRegionChina": "China", - "SettingsTabSystemSystemRegionKorea": "Korea", - "SettingsTabSystemSystemRegionTaiwan": "Taiwan", - "SettingsTabSystemSystemLanguage": "System Language:", - "SettingsTabSystemSystemLanguageJapanese": "Japanese", - "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", - "SettingsTabSystemSystemLanguageFrench": "French", - "SettingsTabSystemSystemLanguageGerman": "German", - "SettingsTabSystemSystemLanguageItalian": "Italian", - "SettingsTabSystemSystemLanguageSpanish": "Spanish", - "SettingsTabSystemSystemLanguageChinese": "Chinese", - "SettingsTabSystemSystemLanguageKorean": "Korean", - "SettingsTabSystemSystemLanguageDutch": "Dutch", - "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", - "SettingsTabSystemSystemLanguageRussian": "Russian", - "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", - "SettingsTabSystemSystemLanguageBritishEnglish": "British English", - "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", - "SettingsTabSystemSystemTimeZone": "System TimeZone:", - "SettingsTabSystemSystemTime": "System Time:", - "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", - "SettingsTabSystemAudioBackend": "Audio Backend:", - "SettingsTabSystemAudioBackendDummy": "Dummy", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": "May cause instability", - "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)", - "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", - "SettingsTabGraphics": "Graphics", - "SettingsTabGraphicsAPI": "Graphics API", - "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", - "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Resolution Scale:", - "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", - "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", - "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", - "SettingsTabGraphicsDeveloperOptions": "Developer Options", - "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", - "SettingsTabLogging": "Logging", - "SettingsTabLoggingLogging": "Logging", - "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", - "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", - "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", - "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", - "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", - "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", - "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", - "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", - "SettingsTabLoggingDeveloperOptions": "Developer Options", - "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", - "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "None", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Error", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "All", - "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", - "SettingsTabInput": "Input", - "SettingsTabInputEnableDockedMode": "Docked Mode", - "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", - "SettingsButtonSave": "Save", - "SettingsButtonClose": "Close", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Cancel", - "SettingsButtonApply": "Apply", - "ControllerSettingsPlayer": "Player", - "ControllerSettingsPlayer1": "Player 1", - "ControllerSettingsPlayer2": "Player 2", - "ControllerSettingsPlayer3": "Player 3", - "ControllerSettingsPlayer4": "Player 4", - "ControllerSettingsPlayer5": "Player 5", - "ControllerSettingsPlayer6": "Player 6", - "ControllerSettingsPlayer7": "Player 7", - "ControllerSettingsPlayer8": "Player 8", - "ControllerSettingsHandheld": "Handheld", - "ControllerSettingsInputDevice": "Input Device", - "ControllerSettingsRefresh": "Refresh", - "ControllerSettingsDeviceDisabled": "Disabled", - "ControllerSettingsControllerType": "Controller Type", - "ControllerSettingsControllerTypeHandheld": "Handheld", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", - "ControllerSettingsProfile": "Profile", - "ControllerSettingsProfileDefault": "Default", - "ControllerSettingsLoad": "Load", - "ControllerSettingsAdd": "Add", - "ControllerSettingsRemove": "Remove", - "ControllerSettingsButtons": "Buttons", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Directional Pad", - "ControllerSettingsDPadUp": "Up", - "ControllerSettingsDPadDown": "Down", - "ControllerSettingsDPadLeft": "Left", - "ControllerSettingsDPadRight": "Right", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", - "ControllerSettingsLStick": "Left Stick", - "ControllerSettingsRStick": "Right Stick", - "ControllerSettingsTriggersLeft": "Triggers Left", - "ControllerSettingsTriggersRight": "Triggers Right", - "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", - "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", - "ControllerSettingsTriggers": "Triggers", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Buttons Left", - "ControllerSettingsExtraButtonsRight": "Buttons Right", - "ControllerSettingsMisc": "Miscellaneous", - "ControllerSettingsTriggerThreshold": "Trigger Threshold:", - "ControllerSettingsMotion": "Motion", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", - "ControllerSettingsMotionControllerSlot": "Controller Slot:", - "ControllerSettingsMotionMirrorInput": "Mirror Input", - "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", - "ControllerSettingsMotionServerHost": "Server Host:", - "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", - "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", - "ControllerSettingsSave": "Save", - "ControllerSettingsClose": "Close", - "UserProfilesSelectedUserProfile": "Selected User Profile:", - "UserProfilesSaveProfileName": "Save Profile Name", - "UserProfilesChangeProfileImage": "Change Profile Image", - "UserProfilesAvailableUserProfiles": "Available User Profiles:", - "UserProfilesAddNewProfile": "Create Profile", - "UserProfilesDelete": "Delete", - "UserProfilesClose": "Close", - "ProfileNameSelectionWatermark": "Choose a nickname", - "ProfileImageSelectionTitle": "Profile Image Selection", - "ProfileImageSelectionHeader": "Choose a profile Image", - "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", - "ProfileImageSelectionImportImage": "Import Image File", - "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", - "InputDialogTitle": "Input Dialog", - "InputDialogOk": "OK", - "InputDialogCancel": "Cancel", - "InputDialogAddNewProfileTitle": "Choose the Profile Name", - "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", - "InputDialogAddNewProfileSubtext": "(Max Length: {0})", - "AvatarChoose": "Choose Avatar", - "AvatarSetBackgroundColor": "Set Background Color", - "AvatarClose": "Close", - "ControllerSettingsLoadProfileToolTip": "Load Profile", - "ControllerSettingsAddProfileToolTip": "Add Profile", - "ControllerSettingsRemoveProfileToolTip": "Remove Profile", - "ControllerSettingsSaveProfileToolTip": "Save Profile", - "MenuBarFileToolsTakeScreenshot": "Take Screenshot", - "MenuBarFileToolsHideUi": "Hide UI", - "GameListContextMenuRunApplication": "Run Application", - "GameListContextMenuToggleFavorite": "Toggle Favorite", - "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", - "SettingsTabGeneralTheme": "Theme:", - "SettingsTabGeneralThemeDark": "Dark", - "SettingsTabGeneralThemeLight": "Light", - "ControllerSettingsConfigureGeneral": "Configure", - "ControllerSettingsRumble": "Rumble", - "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", - "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", - "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", - "DialogConfirmationTitle": "Ryujinx - Confirmation", - "DialogUpdaterTitle": "Ryujinx - Updater", - "DialogErrorTitle": "Ryujinx - Error", - "DialogWarningTitle": "Ryujinx - Warning", - "DialogExitTitle": "Ryujinx - Exit", - "DialogErrorMessage": "Ryujinx has encountered an error", - "DialogExitMessage": "Are you sure you want to close Ryujinx?", - "DialogExitSubMessage": "All unsaved data will be lost!", - "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", - "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", - "FolderDialogExtractTitle": "Choose the folder to extract into", - "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", - "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", - "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", - "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", - "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", - "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", - "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", - "DialogUpdaterDownloadingMessage": "Downloading Update...", - "DialogUpdaterExtractionMessage": "Extracting Update...", - "DialogUpdaterRenamingMessage": "Renaming Update...", - "DialogUpdaterAddingFilesMessage": "Adding New Update...", - "DialogUpdaterCompleteMessage": "Update Complete!", - "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", - "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", - "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", - "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", - "DialogRestartRequiredMessage": "Restart Required", - "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", - "DialogThemeRestartSubMessage": "Do you want to restart", - "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", - "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", - "DialogFirmwareInstalledMessage": "Firmware {0} was installed", - "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", - "DialogInstallFileTypesErrorMessage": "Failed to install file types.", - "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", - "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", - "DialogOpenSettingsWindowLabel": "Open Settings Window", - "DialogControllerAppletTitle": "Controller Applet", - "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", - "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", - "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", - "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", - "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", - "DialogProfileDeleteProfileTitle": "Deleting Profile", - "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", - "DialogWarning": "Warning", - "DialogPPTCDeletionMessage": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?", - "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", - "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", - "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", - "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", - "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", - "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", - "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", - "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", - "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", - "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", - "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", - "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", - "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", - "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", - "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", - "SettingsTabGraphicsFeaturesOptions": "Features", - "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", - "CommonAuto": "Auto", - "CommonOff": "Off", - "CommonOn": "On", - "InputDialogYes": "Yes", - "InputDialogNo": "No", - "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", - "MenuBarOptionsPauseEmulation": "Pause", - "MenuBarOptionsResumeEmulation": "Resume", - "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", - "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", - "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", - "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", - "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", - "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", - "AboutRyujinxAboutTitle": "About:", - "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", - "AboutRyujinxMaintainersTitle": "Maintained By:", - "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", - "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", - "AmiiboSeriesLabel": "Amiibo Series", - "AmiiboCharacterLabel": "Character", - "AmiiboScanButtonLabel": "Scan It", - "AmiiboOptionsShowAllLabel": "Show All Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", - "DlcManagerTableHeadingEnabledLabel": "Enabled", - "DlcManagerTableHeadingTitleIdLabel": "Title ID", - "DlcManagerTableHeadingContainerPathLabel": "Container Path", - "DlcManagerTableHeadingFullPathLabel": "Full Path", - "DlcManagerRemoveAllButton": "Remove All", - "DlcManagerEnableAllButton": "Enable All", - "DlcManagerDisableAllButton": "Disable All", - "ModManagerDeleteAllButton": "Delete All", - "MenuBarOptionsChangeLanguage": "Change Language", - "MenuBarShowFileTypes": "Show File Types", - "CommonSort": "Sort", - "CommonShowNames": "Show Names", - "CommonFavorite": "Favorite", - "OrderAscending": "Ascending", - "OrderDescending": "Descending", - "SettingsTabGraphicsFeatures": "Features & Enhancements", - "ErrorWindowTitle": "Error Window", - "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", - "AddGameDirBoxTooltip": "Enter a game directory to add to the list", - "AddGameDirTooltip": "Add a game directory to the list", - "RemoveGameDirTooltip": "Remove selected game directory", - "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", - "CustomThemePathTooltip": "Path to custom GUI theme", - "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", - "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", - "RegionTooltip": "Change System Region", - "LanguageTooltip": "Change System Language", - "TimezoneTooltip": "Change System TimeZone", - "TimeTooltip": "Change System Time", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", - "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", - "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", - "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", - "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", - "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", - "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", - "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", - "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", - "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", - "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", - "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", - "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", - "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", - "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", - "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", - "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", - "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", - "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", - "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", - "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", - "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", - "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", - "FileAccessLogTooltip": "Prints file access log messages in the console.", - "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", - "DeveloperOptionTooltip": "Use with care", - "OpenGlLogLevel": "Requires appropriate log levels enabled", - "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", - "LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load", - "LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load", - "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", - "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", - "ExitTooltip": "Exit Ryujinx", - "OpenSettingsTooltip": "Open settings window", - "OpenProfileManagerTooltip": "Open User Profiles Manager window", - "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", - "CheckUpdatesTooltip": "Check for updates to Ryujinx", - "OpenAboutTooltip": "Open About Window", - "GridSize": "Grid Size", - "GridSizeTooltip": "Change the size of grid items", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", - "AboutRyujinxContributorsButtonHeader": "See All Contributors", - "SettingsTabSystemAudioVolume": "Volume: ", - "AudioVolumeTooltip": "Change Audio Volume", - "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", - "EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.", - "GameListContextMenuManageCheatToolTip": "Manage Cheats", - "GameListContextMenuManageCheat": "Manage Cheats", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", - "ControllerSettingsStickRange": "Range:", - "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", - "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Audio", - "SettingsTabNetwork": "Network", - "SettingsTabNetworkConnection": "Network Connection", - "SettingsTabCpuCache": "CPU Cache", - "SettingsTabCpuMemory": "CPU Mode", - "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", - "UpdaterDisabledWarningTitle": "Updater Disabled!", - "ControllerSettingsRotate90": "Rotate 90° Clockwise", - "IconSize": "Icon Size", - "IconSizeTooltip": "Change the size of game icons", - "MenuBarOptionsShowConsole": "Show Console", - "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", - "UserErrorNoKeys": "Keys not found", - "UserErrorNoFirmware": "Firmware not found", - "UserErrorFirmwareParsingFailed": "Firmware parsing error", - "UserErrorApplicationNotFound": "Application not found", - "UserErrorUnknown": "Unknown error", - "UserErrorUndefined": "Undefined error", - "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", - "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", - "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", - "UserErrorUnknownDescription": "An unknown error occured!", - "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", - "OpenSetupGuideMessage": "Open the Setup Guide", - "NoUpdate": "No Update", - "TitleUpdateVersionLabel": "Version {0}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Confirmation", - "FileDialogAllTypes": "All types", - "Never": "Never", - "SwkbdMinCharacters": "Must be at least {0} characters long", - "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", - "SoftwareKeyboard": "Software Keyboard", - "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "ControllerAppletControllers": "Supported Controllers:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", - "UpdaterRenaming": "Renaming Old Files...", - "UpdaterRenameFailed": "Updater was unable to rename file: {0}", - "UpdaterAddingFiles": "Adding New Files...", - "UpdaterExtracting": "Extracting Update...", - "UpdaterDownloading": "Downloading Update...", - "Game": "Game", - "Docked": "Docked", - "Handheld": "Handheld", - "ConnectionError": "Connection Error.", - "AboutPageDeveloperListMore": "{0} and more...", - "ApiError": "API Error.", - "LoadingHeading": "Loading {0}", - "CompilingPPTC": "Compiling PTC", - "CompilingShaders": "Compiling Shaders", - "AllKeyboards": "All keyboards", - "OpenFileDialogTitle": "Select a supported file to open", - "OpenFolderDialogTitle": "Select a folder with an unpacked game", - "AllSupportedFormats": "All Supported Formats", - "RyujinxUpdater": "Ryujinx Updater", - "SettingsTabHotkeys": "Keyboard Hotkeys", - "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", - "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", - "SettingsTabHotkeysShowUiHotkey": "Show UI:", - "SettingsTabHotkeysPauseHotkey": "Pause:", - "SettingsTabHotkeysToggleMuteHotkey": "Mute:", - "ControllerMotionTitle": "Motion Control Settings", - "ControllerRumbleTitle": "Rumble Settings", - "SettingsSelectThemeFileDialogTitle": "Select Theme File", - "SettingsXamlThemeFile": "Xaml Theme File", - "AvatarWindowTitle": "Manage Accounts - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Unknown", - "Usage": "Usage", - "Writable": "Writable", - "SelectDlcDialogTitle": "Select DLC files", - "SelectUpdateDialogTitle": "Select update files", - "SelectModDialogTitle": "Select mod directory", - "UserProfileWindowTitle": "User Profiles Manager", - "CheatWindowTitle": "Cheats Manager", - "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", - "UpdateWindowTitle": "Title Update Manager", - "CheatWindowHeading": "Cheats Available for {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "{0} Downloadable Content(s)", - "ModWindowHeading": "{0} Mod(s)", - "UserProfilesEditProfile": "Edit Selected", - "Cancel": "Cancel", - "Save": "Save", - "Discard": "Discard", - "Paused": "Paused", - "UserProfilesSetProfileImage": "Set Profile Image", - "UserProfileEmptyNameError": "Name is required", - "UserProfileNoImageError": "Profile image must be set", - "GameUpdateWindowHeading": "Manage Updates for {0} ({1})", - "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", - "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", - "UserProfilesName": "Name:", - "UserProfilesUserId": "User ID:", - "SettingsTabGraphicsBackend": "Graphics Backend", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", - "SettingsEnableTextureRecompression": "Enable Texture Recompression", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", - "SettingsTabGraphicsPreferredGpu": "Preferred GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", - "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", - "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", - "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", - "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", - "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", - "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", - "SettingsEnableMacroHLE": "Enable Macro HLE", - "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Vol", - "UserProfilesManageSaves": "Manage Saves", - "DeleteUserSave": "Do you want to delete user save for this game?", - "IrreversibleActionNote": "This action is not reversible.", - "SaveManagerHeading": "Manage Saves for {0} ({1})", - "SaveManagerTitle": "Save Manager", - "Name": "Name", - "Size": "Size", - "Search": "Search", - "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", - "Recover": "Recover", - "UserProfilesRecoverHeading": "Saves were found for the following accounts", - "UserProfilesRecoverEmptyList": "No profiles to recover", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", - "GraphicsAALabel": "Anti-Aliasing:", - "GraphicsScalingFilterLabel": "Scaling Filter:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", - "GraphicsScalingFilterLevelLabel": "Level", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", - "SmaaLow": "SMAA Low", - "SmaaMedium": "SMAA Medium", - "SmaaHigh": "SMAA High", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Edit User", - "UserEditorTitleCreate": "Create User", - "SettingsTabNetworkInterface": "Network Interface:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", - "NetworkInterfaceDefault": "Default", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", - "SettingsTabNetworkMultiplayer": "Multiplayer", - "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." -} diff --git a/src/Ryujinx.Ava/Assets/Locales/es_ES.json b/src/Ryujinx.Ava/Assets/Locales/es_ES.json deleted file mode 100644 index 91bcd8f1..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/es_ES.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Español (ES)", - "MenuBarFileOpenApplet": "Abrir applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", - "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", - "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", - "SettingsTabSystemMemoryManagerModeSoftware": "Software", - "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", - "SettingsTabSystemUseHypervisor": "Usar hipervisor", - "MenuBarFile": "_Archivo", - "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", - "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", - "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", - "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", - "MenuBarFileExit": "_Salir", - "MenuBarOptions": "_Opciones", - "MenuBarOptionsToggleFullscreen": "Cambiar a pantalla completa.", - "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", - "MenuBarOptionsStopEmulation": "Detener emulación", - "MenuBarOptionsSettings": "_Configuración", - "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", - "MenuBarActions": "_Acciones", - "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", - "MenuBarActionsScanAmiibo": "Escanear Amiibo", - "MenuBarTools": "_Herramientas", - "MenuBarToolsInstallFirmware": "Instalar firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", - "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", - "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", - "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", - "MenuBarHelp": "Ayuda", - "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", - "MenuBarHelpAbout": "Acerca de", - "MenuSearch": "Buscar...", - "GameListHeaderFavorite": "Favoritos", - "GameListHeaderIcon": "Icono", - "GameListHeaderApplication": "Nombre", - "GameListHeaderDeveloper": "Desarrollador", - "GameListHeaderVersion": "Versión", - "GameListHeaderTimePlayed": "Tiempo jugado", - "GameListHeaderLastPlayed": "Jugado por última vez", - "GameListHeaderFileExtension": "Extensión", - "GameListHeaderFileSize": "Tamaño del archivo", - "GameListHeaderPath": "Directorio", - "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", - "GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", - "GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación", - "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", - "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", - "GameListContextMenuManageDlc": "Gestionar DLC", - "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", - "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", - "GameListContextMenuCacheManagement": "Gestión de caché ", - "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", - "GameListContextMenuExtractData": "Extraer datos", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", - "GameListContextMenuExtractDataLogo": "Logotipo", - "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", - "StatusBarGamesLoaded": "{0}/{1} juegos cargados", - "StatusBarSystemVersion": "Versión del sistema: {0}", - "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", - "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", - "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sí, hasta el próximo reinicio", - "LinuxVmMaxMapCountDialogButtonPersistent": "Si, permanentemente", - "LinuxVmMaxMapCountWarningTextPrimary": "La cantidad máxima de mapeos de memoria es menor de lo recomendado.", - "LinuxVmMaxMapCountWarningTextSecondary": "El valor actual de vm.max_map_count ({0}) es menor que {1}. Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos actualmente. Ryujinx se bloqueará tan pronto como se supere este límite.\n\nPuede que desee aumentar manualmente el límite o instalar pkexec, lo que permite a Ryujinx ayudar con eso.", - "Settings": "Configuración", - "SettingsTabGeneral": "Interfaz de usuario", - "SettingsTabGeneralGeneral": "General", - "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", - "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", - "SettingsTabGeneralHideCursor": "Esconder el cursor:", - "SettingsTabGeneralHideCursorNever": "Nunca", - "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", - "SettingsTabGeneralHideCursorAlways": "Siempre", - "SettingsTabGeneralGameDirectories": "Carpetas de juegos", - "SettingsTabGeneralAdd": "Agregar", - "SettingsTabGeneralRemove": "Quitar", - "SettingsTabSystem": "Sistema", - "SettingsTabSystemCore": "Núcleo", - "SettingsTabSystemSystemRegion": "Región del sistema:", - "SettingsTabSystemSystemRegionJapan": "Japón", - "SettingsTabSystemSystemRegionUSA": "EEUU", - "SettingsTabSystemSystemRegionEurope": "Europa", - "SettingsTabSystemSystemRegionAustralia": "Australia", - "SettingsTabSystemSystemRegionChina": "China", - "SettingsTabSystemSystemRegionKorea": "Corea", - "SettingsTabSystemSystemRegionTaiwan": "Taiwán", - "SettingsTabSystemSystemLanguage": "Idioma del sistema:", - "SettingsTabSystemSystemLanguageJapanese": "Japonés", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", - "SettingsTabSystemSystemLanguageFrench": "Francés", - "SettingsTabSystemSystemLanguageGerman": "Alemán", - "SettingsTabSystemSystemLanguageItalian": "Italiano", - "SettingsTabSystemSystemLanguageSpanish": "Español", - "SettingsTabSystemSystemLanguageChinese": "Chino", - "SettingsTabSystemSystemLanguageKorean": "Coreano", - "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", - "SettingsTabSystemSystemLanguagePortuguese": "Portugués", - "SettingsTabSystemSystemLanguageRussian": "Ruso", - "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", - "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", - "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", - "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", - "SettingsTabSystemSystemTime": "Hora del sistema:", - "SettingsTabSystemEnableVsync": "Sincronización vertical", - "SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)", - "SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos", - "SettingsTabSystemAudioBackend": "Motor de audio:", - "SettingsTabSystemAudioBackendDummy": "Vacio", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", - "SettingsTabSystemExpandDramSize": "Usar diseño alternativo de memoria (Desarrolladores)", - "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", - "SettingsTabGraphics": "Gráficos", - "SettingsTabGraphicsAPI": "API de gráficos", - "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", - "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", - "SettingsTabGraphicsAnisotropicFiltering2x": "x2", - "SettingsTabGraphicsAnisotropicFiltering4x": "x4", - "SettingsTabGraphicsAnisotropicFiltering8x": "x8", - "SettingsTabGraphicsAnisotropicFiltering16x": "x16", - "SettingsTabGraphicsResolutionScale": "Escala de resolución:", - "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", - "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", - "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", - "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", - "SettingsTabLogging": "Registros", - "SettingsTabLoggingLogging": "Registros", - "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", - "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", - "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", - "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", - "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", - "SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro", - "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", - "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", - "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", - "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", - "SettingsTabLoggingDeveloperOptionsNote": "ADVERTENCIA: Reducirá el rendimiento", - "SettingsTabLoggingGraphicsBackendLogLevel": "Nivel de registro de backend gráficos:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Errores", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo", - "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", - "SettingsTabInput": "Entrada", - "SettingsTabInputEnableDockedMode": "Modo dock/TV", - "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", - "SettingsButtonSave": "Guardar", - "SettingsButtonClose": "Cerrar", - "SettingsButtonOk": "Aceptar", - "SettingsButtonCancel": "Cancelar", - "SettingsButtonApply": "Aplicar", - "ControllerSettingsPlayer": "Jugador", - "ControllerSettingsPlayer1": "Jugador 1", - "ControllerSettingsPlayer2": "Jugador 2", - "ControllerSettingsPlayer3": "Jugador 3", - "ControllerSettingsPlayer4": "Jugador 4", - "ControllerSettingsPlayer5": "Jugador 5", - "ControllerSettingsPlayer6": "Jugador 6", - "ControllerSettingsPlayer7": "Jugador 7", - "ControllerSettingsPlayer8": "Jugador 8", - "ControllerSettingsHandheld": "Portátil", - "ControllerSettingsInputDevice": "Dispositivo de entrada", - "ControllerSettingsRefresh": "Actualizar", - "ControllerSettingsDeviceDisabled": "Deshabilitado", - "ControllerSettingsControllerType": "Tipo de Mando", - "ControllerSettingsControllerTypeHandheld": "Portátil", - "ControllerSettingsControllerTypeProController": "Mando Pro", - "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", - "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", - "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", - "ControllerSettingsProfile": "Perfil", - "ControllerSettingsProfileDefault": "Predeterminado", - "ControllerSettingsLoad": "Cargar", - "ControllerSettingsAdd": "Agregar", - "ControllerSettingsRemove": "Quitar", - "ControllerSettingsButtons": "Botones", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Pad direccional", - "ControllerSettingsDPadUp": "Arriba", - "ControllerSettingsDPadDown": "Abajo", - "ControllerSettingsDPadLeft": "Izquierda", - "ControllerSettingsDPadRight": "Derecha", - "ControllerSettingsStickButton": "Botón", - "ControllerSettingsStickUp": "Arriba", - "ControllerSettingsStickDown": "Abajo", - "ControllerSettingsStickLeft": "Izquierda", - "ControllerSettingsStickRight": "Derecha", - "ControllerSettingsStickStick": "Palanca", - "ControllerSettingsStickInvertXAxis": "Invertir eje X", - "ControllerSettingsStickInvertYAxis": "Invertir eje Y", - "ControllerSettingsStickDeadzone": "Zona muerta:", - "ControllerSettingsLStick": "Palanca izquierda", - "ControllerSettingsRStick": "Palanca derecha", - "ControllerSettingsTriggersLeft": "Gatillos izquierdos", - "ControllerSettingsTriggersRight": "Gatillos derechos", - "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", - "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", - "ControllerSettingsTriggers": "Gatillos", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", - "ControllerSettingsExtraButtonsRight": "Botones derechos", - "ControllerSettingsMisc": "Misceláneo", - "ControllerSettingsTriggerThreshold": "Límite de gatillos:", - "ControllerSettingsMotion": "Movimiento", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", - "ControllerSettingsMotionControllerSlot": "Puerto del mando:", - "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", - "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", - "ControllerSettingsMotionServerHost": "Host del servidor:", - "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", - "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", - "ControllerSettingsSave": "Guardar", - "ControllerSettingsClose": "Cerrar", - "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", - "UserProfilesSaveProfileName": "Guardar nombre de perfil", - "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", - "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", - "UserProfilesAddNewProfile": "Añadir nuevo perfil", - "UserProfilesDelete": "Eliminar", - "UserProfilesClose": "Cerrar", - "ProfileNameSelectionWatermark": "Escoge un apodo", - "ProfileImageSelectionTitle": "Selección de imagen de perfil", - "ProfileImageSelectionHeader": "Elige una imagen de perfil", - "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", - "ProfileImageSelectionImportImage": "Importar imagen", - "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", - "InputDialogTitle": "Cuadro de diálogo de entrada", - "InputDialogOk": "Aceptar", - "InputDialogCancel": "Cancelar", - "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", - "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", - "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", - "AvatarChoose": "Escoger", - "AvatarSetBackgroundColor": "Establecer color de fondo", - "AvatarClose": "Cerrar", - "ControllerSettingsLoadProfileToolTip": "Cargar perfil", - "ControllerSettingsAddProfileToolTip": "Agregar perfil", - "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", - "ControllerSettingsSaveProfileToolTip": "Guardar perfil", - "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", - "MenuBarFileToolsHideUi": "Ocultar interfaz", - "GameListContextMenuRunApplication": "Ejecutar aplicación", - "GameListContextMenuToggleFavorite": "Marcar favorito", - "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", - "ButtonBrowse": "Buscar", - "ControllerSettingsConfigureGeneral": "Configurar", - "ControllerSettingsRumble": "Vibración", - "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", - "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", - "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", - "DialogConfirmationTitle": "Ryujinx - Confirmación", - "DialogUpdaterTitle": "Ryujinx - Actualizador", - "DialogErrorTitle": "Ryujinx - Error", - "DialogWarningTitle": "Ryujinx - Advertencia", - "DialogExitTitle": "Ryujinx - Salir", - "DialogErrorMessage": "Ryujinx encontró un error", - "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", - "DialogExitSubMessage": "¡Se perderán los datos no guardados!", - "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", - "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", - "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", - "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", - "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", - "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", - "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Se ha producido un error al intentar obtener información de liberación de GitHub Release. Esto puede ser causado si una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.", - "DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.", - "DialogUpdaterDownloadingMessage": "Descargando actualización...", - "DialogUpdaterExtractionMessage": "Extrayendo actualización...", - "DialogUpdaterRenamingMessage": "Renombrando actualización...", - "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", - "DialogUpdaterCompleteMessage": "¡Actualización completa!", - "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", - "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", - "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", - "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", - "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", - "DialogRestartRequiredMessage": "Se necesita reiniciar", - "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", - "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", - "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", - "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", - "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", - "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", - "DialogInstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", - "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", - "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", - "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", - "DialogControllerAppletTitle": "Applet de mandos", - "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", - "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", - "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", - "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", - "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", - "DialogProfileDeleteProfileTitle": "Eliminando perfil", - "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", - "DialogWarning": "Advertencia", - "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", - "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", - "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", - "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", - "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", - "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", - "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", - "DialogUserProfileUnsavedChangesTitle": "Advertencia - Cambios sin guardar", - "DialogUserProfileUnsavedChangesMessage": "Ha realizado cambios en este perfil de usuario que no han sido guardados.", - "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", - "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", - "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", - "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", - "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", - "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", - "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", - "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", - "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", - "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", - "CommonAuto": "Automático", - "CommonOff": "Desactivado", - "CommonOn": "Activado", - "InputDialogYes": "Sí", - "InputDialogNo": "No", - "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", - "MenuBarOptionsPauseEmulation": "Pausar", - "MenuBarOptionsResumeEmulation": "Reanudar", - "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", - "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", - "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", - "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", - "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", - "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", - "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", - "AboutRyujinxAboutTitle": "Acerca de:", - "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", - "AboutRyujinxMaintainersTitle": "Mantenido por:", - "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", - "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", - "AmiiboSeriesLabel": "Serie de Amiibo", - "AmiiboCharacterLabel": "Personaje", - "AmiiboScanButtonLabel": "Escanear", - "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", - "DlcManagerTableHeadingEnabledLabel": "Habilitado", - "DlcManagerTableHeadingTitleIdLabel": "ID de título", - "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", - "DlcManagerTableHeadingFullPathLabel": "Directorio completo", - "DlcManagerRemoveAllButton": "Quitar todo", - "DlcManagerEnableAllButton": "Activar todas", - "DlcManagerDisableAllButton": "Desactivar todos", - "MenuBarOptionsChangeLanguage": "Cambiar idioma", - "MenuBarShowFileTypes": "Mostrar tipos de archivo", - "CommonSort": "Orden", - "CommonShowNames": "Mostrar nombres", - "CommonFavorite": "Favorito", - "OrderAscending": "Ascendente", - "OrderDescending": "Descendente", - "SettingsTabGraphicsFeatures": "Funcionalidades Y Mejoras", - "ErrorWindowTitle": "Ventana de error", - "ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando", - "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", - "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", - "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", - "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", - "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", - "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", - "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", - "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", - "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", - "RegionTooltip": "Cambia la región del sistema", - "LanguageTooltip": "Cambia el idioma del sistema", - "TimezoneTooltip": "Cambia la zona horaria del sistema", - "TimeTooltip": "Cambia la hora del sistema", - "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", - "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", - "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", - "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", - "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", - "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", - "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", - "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", - "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", - "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", - "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", - "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", - "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", - "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", - "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", - "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", - "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", - "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", - "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", - "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", - "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", - "InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.", - "WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.", - "ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.", - "TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.", - "GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.", - "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", - "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", - "DeveloperOptionTooltip": "Usar con cuidado", - "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", - "DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", - "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", - "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", - "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", - "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", - "ExitTooltip": "Cierra Ryujinx", - "OpenSettingsTooltip": "Abre la ventana de configuración", - "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", - "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", - "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", - "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", - "GridSize": "Tamaño de cuadrícula", - "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", - "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", - "SettingsTabSystemAudioVolume": "Volumen: ", - "AudioVolumeTooltip": "Ajusta el nivel de volumen", - "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", - "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", - "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", - "GameListContextMenuManageCheat": "Administrar cheats", - "ControllerSettingsStickRange": "Alcance:", - "DialogStopEmulationTitle": "Ryujinx - Detener emulación", - "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Sonido", - "SettingsTabNetwork": "Red", - "SettingsTabNetworkConnection": "Conexión de red", - "SettingsTabCpuCache": "Caché de CPU", - "SettingsTabCpuMemory": "Memoria de CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", - "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", - "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", - "IconSize": "Tamaño de iconos", - "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", - "MenuBarOptionsShowConsole": "Mostrar consola", - "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", - "UserErrorNoKeys": "No se encontraron keys", - "UserErrorNoFirmware": "No se encontró firmware", - "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", - "UserErrorApplicationNotFound": "No se encontró la aplicación", - "UserErrorUnknown": "Error desconocido", - "UserErrorUndefined": "Error indefinido", - "UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.", - "UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.", - "UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.", - "UserErrorUnknownDescription": "¡Ocurrió un error desconocido!", - "UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!", - "OpenSetupGuideMessage": "Abrir la guía de instalación", - "NoUpdate": "No actualizado", - "TitleUpdateVersionLabel": "Versión {0} - {1}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Confirmación", - "FileDialogAllTypes": "Todos los tipos", - "Never": "Nunca", - "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", - "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", - "SoftwareKeyboard": "Teclado de software", - "SoftwareKeyboardModeNumbersOnly": "Solo deben ser números", - "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", - "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", - "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", - "UpdaterRenaming": "Renombrando archivos viejos...", - "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", - "UpdaterAddingFiles": "Añadiendo nuevos archivos...", - "UpdaterExtracting": "Extrayendo actualización...", - "UpdaterDownloading": "Descargando actualización...", - "Game": "Juego", - "Docked": "Dock/TV", - "Handheld": "Portátil", - "ConnectionError": "Error de conexión.", - "AboutPageDeveloperListMore": "{0} y más...", - "ApiError": "Error de API.", - "LoadingHeading": "Cargando {0}", - "CompilingPPTC": "Compilando PTC", - "CompilingShaders": "Compilando sombreadores", - "AllKeyboards": "Todos los teclados", - "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", - "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", - "AllSupportedFormats": "Todos los formatos soportados", - "RyujinxUpdater": "Actualizador de Ryujinx", - "SettingsTabHotkeys": "Atajos de teclado", - "SettingsTabHotkeysHotkeys": "Atajos de teclado", - "SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:", - "SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:", - "SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:", - "SettingsTabHotkeysPauseHotkey": "Pausar:", - "SettingsTabHotkeysToggleMuteHotkey": "Silenciar:", - "ControllerMotionTitle": "Opciones de controles de movimiento", - "ControllerRumbleTitle": "Opciones de vibración", - "SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema", - "SettingsXamlThemeFile": "Archivo de tema Xaml", - "AvatarWindowTitle": "Administrar cuentas - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Desconocido", - "Usage": "Uso", - "Writable": "Escribible", - "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", - "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", - "UserProfileWindowTitle": "Administrar perfiles de usuario", - "CheatWindowTitle": "Administrar cheats", - "DlcWindowTitle": "Administrar contenido descargable", - "UpdateWindowTitle": "Administrar actualizaciones", - "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", - "BuildId": "Id de compilación:", - "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", - "UserProfilesEditProfile": "Editar selección", - "Cancel": "Cancelar", - "Save": "Guardar", - "Discard": "Descartar", - "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", - "UserProfileEmptyNameError": "El nombre es obligatorio", - "UserProfileNoImageError": "Debe establecerse la imagen de perfil", - "GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:", - "SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:", - "UserProfilesName": "Nombre:", - "UserProfilesUserId": "Id de Usuario:", - "SettingsTabGraphicsBackend": "Fondo de gráficos", - "SettingsTabGraphicsBackendTooltip": "Back-end de los gráficos a utilizar", - "SettingsEnableTextureRecompression": "Activar recompresión de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime ciertas texturas para reducir el uso de la VRAM.\n\nRecomendado para GPUs que tienen menos de 4 GB de VRAM.\n\nMantén esta opción desactivada si no estás seguro.", - "SettingsTabGraphicsPreferredGpu": "GPU preferida", - "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", - "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", - "SettingsGpuBackendRestartMessage": "La configuración de la GPU o del back-end de los gráficos fue modificada. Es necesario reiniciar para que se aplique.", - "SettingsGpuBackendRestartSubMessage": "¿Quieres reiniciar ahora?", - "RyujinxUpdaterMessage": "¿Quieres actualizar Ryujinx a la última versión?", - "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volumen:", - "SettingsTabHotkeysVolumeDownHotkey": "Disminuir volumen:", - "SettingsEnableMacroHLE": "Activar Macros HLE", - "SettingsEnableMacroHLETooltip": "Emulación alto-nivel del código de Macros de GPU\n\nIncrementa el rendimiento, pero puede causar errores gráficos en algunos juegos.\n\nDeja esta opción activada si no estás seguro.", - "SettingsEnableColorSpacePassthrough": "Paso de espacio de color", - "SettingsEnableColorSpacePassthroughTooltip": "Dirige el backend de Vulkan a pasar a través de la información del color sin especificar un espacio de color. Para los usuarios con pantallas de gran gama, esto puede resultar en colores más vibrantes, a costa de la corrección del color.", - "VolumeShort": "Volumen", - "UserProfilesManageSaves": "Administrar mis partidas guardadas", - "DeleteUserSave": "¿Quieres borrar los datos de usuario de este juego?", - "IrreversibleActionNote": "Esta acción no es reversible.", - "SaveManagerHeading": "Manage Saves for {0}", - "SaveManagerTitle": "Administrador de datos de guardado.", - "Name": "Nombre", - "Size": "Tamaño", - "Search": "Buscar", - "UserProfilesRecoverLostAccounts": "Recuperar cuentas perdidas", - "Recover": "Recuperar", - "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", - "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", - "GraphicsAATooltip": "Aplica el suavizado de bordes al procesamiento del juego", - "GraphicsAALabel": "Suavizado de bordes:", - "GraphicsScalingFilterLabel": "Filtro de escalado:", - "GraphicsScalingFilterTooltip": "Activa el escalado de búfer de fotogramas", - "GraphicsScalingFilterLevelLabel": "Nivel", - "GraphicsScalingFilterLevelTooltip": "Establecer nivel del filtro de escalado", - "SmaaLow": "SMAA Bajo", - "SmaaMedium": "SMAA Medio", - "SmaaHigh": "SMAA Alto", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Editar usuario", - "UserEditorTitleCreate": "Crear Usuario", - "SettingsTabNetworkInterface": "Interfaz de Red", - "NetworkInterfaceTooltip": "Interfaz de red usada para las características LAN", - "NetworkInterfaceDefault": "Predeterminado", - "PackagingShaders": "Empaquetando sombreadores", - "AboutChangelogButton": "Ver registro de cambios en GitHub", - "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/fr_FR.json b/src/Ryujinx.Ava/Assets/Locales/fr_FR.json deleted file mode 100644 index 5bab6f7b..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/fr_FR.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Français", - "MenuBarFileOpenApplet": "Ouvrir Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Éditeur Applet Mii en mode autonome", - "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", - "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", - "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", - "SettingsTabSystemMemoryManagerModeHost": "Hôte (rapide)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hôte non vérifié (plus rapide, non sécurisé)", - "SettingsTabSystemUseHypervisor": "Utiliser l'Hyperviseur", - "MenuBarFile": "_Fichier", - "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", - "MenuBarFileOpenUnpacked": "Charger un jeu extrait", - "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", - "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", - "MenuBarFileExit": "_Quitter", - "MenuBarOptions": "Options", - "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", - "MenuBarOptionsStartGamesInFullscreen": "Démarrer jeux en plein écran", - "MenuBarOptionsStopEmulation": "Arrêter l'émulation", - "MenuBarOptionsSettings": "_Paramètres", - "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", - "MenuBarActions": "_Actions", - "MenuBarOptionsSimulateWakeUpMessage": "Simuler un message de réveil", - "MenuBarActionsScanAmiibo": "Scanner un Amiibo", - "MenuBarTools": "_Outils", - "MenuBarToolsInstallFirmware": "Installer un firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", - "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", - "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", - "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", - "MenuBarHelp": "Aide", - "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", - "MenuBarHelpAbout": "Á propos", - "MenuSearch": "Rechercher...", - "GameListHeaderFavorite": "Favoris", - "GameListHeaderIcon": "Icône", - "GameListHeaderApplication": "Nom", - "GameListHeaderDeveloper": "Développeur", - "GameListHeaderVersion": "Version", - "GameListHeaderTimePlayed": "Temps de jeu", - "GameListHeaderLastPlayed": "jouer la dernière fois", - "GameListHeaderFileExtension": "Fichier externe", - "GameListHeaderFileSize": "Taille du Fichier", - "GameListHeaderPath": "Chemin", - "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", - "GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", - "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", - "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", - "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion de la mise à jour des titres", - "GameListContextMenuManageDlc": "Gérer les DLC", - "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", - "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", - "GameListContextMenuCacheManagement": "Gestion des caches", - "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", - "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", - "GameListContextMenuExtractData": "Extraire les données", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", - "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", - "StatusBarSystemVersion": "Version du Firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappages de mémoire détectés", - "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappages de mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", - "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", - "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", - "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx s'écrasera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", - "Settings": "Paramètres", - "SettingsTabGeneral": "Interface Utilisateur", - "SettingsTabGeneralGeneral": "Général", - "SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", - "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"", - "SettingsTabGeneralHideCursor": "Masquer le Curseur :", - "SettingsTabGeneralHideCursorNever": "Jamais", - "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", - "SettingsTabGeneralHideCursorAlways": "Toujours", - "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", - "SettingsTabGeneralAdd": "Ajouter", - "SettingsTabGeneralRemove": "Supprimer", - "SettingsTabSystem": "Système", - "SettingsTabSystemCore": "Cœur", - "SettingsTabSystemSystemRegion": "Région du système:", - "SettingsTabSystemSystemRegionJapan": "Japon", - "SettingsTabSystemSystemRegionUSA": "USA", - "SettingsTabSystemSystemRegionEurope": "Europe", - "SettingsTabSystemSystemRegionAustralia": "Australie", - "SettingsTabSystemSystemRegionChina": "Chine", - "SettingsTabSystemSystemRegionKorea": "Corée", - "SettingsTabSystemSystemRegionTaiwan": "Taïwan", - "SettingsTabSystemSystemLanguage": "Langue du système:", - "SettingsTabSystemSystemLanguageJapanese": "Japonais", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Anglais Américain", - "SettingsTabSystemSystemLanguageFrench": "Français", - "SettingsTabSystemSystemLanguageGerman": "Allemand", - "SettingsTabSystemSystemLanguageItalian": "Italien", - "SettingsTabSystemSystemLanguageSpanish": "Espagnol", - "SettingsTabSystemSystemLanguageChinese": "Chinois", - "SettingsTabSystemSystemLanguageKorean": "Coréen", - "SettingsTabSystemSystemLanguageDutch": "Néerlandais", - "SettingsTabSystemSystemLanguagePortuguese": "Portugais", - "SettingsTabSystemSystemLanguageRussian": "Russe", - "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", - "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais britannique ", - "SettingsTabSystemSystemLanguageCanadianFrench": "Français Canadien", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", - "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", - "SettingsTabSystemSystemTime": "Heure du système:", - "SettingsTabSystemEnableVsync": "Activer le VSync", - "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", - "SettingsTabSystemAudioBackend": "Back-end audio", - "SettingsTabSystemAudioBackendDummy": "Factice", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", - "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", - "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", - "SettingsTabGraphics": "Graphique", - "SettingsTabGraphicsAPI": "API Graphique", - "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", - "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", - "SettingsTabGraphicsAnisotropicFiltering2x": "x2", - "SettingsTabGraphicsAnisotropicFiltering4x": "x4", - "SettingsTabGraphicsAnisotropicFiltering8x": "x8", - "SettingsTabGraphicsAnisotropicFiltering16x": "x16", - "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", - "SettingsTabGraphicsResolutionScaleCustom": "Personnalisée (Non recommandée)", - "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Format :", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", - "SettingsTabGraphicsDeveloperOptions": "Options développeur", - "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", - "SettingsTabLogging": "Journaux", - "SettingsTabLoggingLogging": "Journaux", - "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", - "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", - "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", - "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", - "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", - "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", - "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", - "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", - "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", - "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", - "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", - "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout", - "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", - "SettingsTabInput": "Contrôles", - "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", - "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", - "SettingsButtonSave": "Enregistrer", - "SettingsButtonClose": "Fermer", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Annuler", - "SettingsButtonApply": "Appliquer", - "ControllerSettingsPlayer": "Joueur", - "ControllerSettingsPlayer1": "Joueur 1", - "ControllerSettingsPlayer2": "Joueur 2", - "ControllerSettingsPlayer3": "Joueur 3", - "ControllerSettingsPlayer4": "Joueur 4", - "ControllerSettingsPlayer5": "Joueur 5", - "ControllerSettingsPlayer6": "Joueur 6", - "ControllerSettingsPlayer7": "Joueur 7", - "ControllerSettingsPlayer8": "Joueur 8", - "ControllerSettingsHandheld": "Portable", - "ControllerSettingsInputDevice": "Périphériques", - "ControllerSettingsRefresh": "Actualiser", - "ControllerSettingsDeviceDisabled": "Désactivé", - "ControllerSettingsControllerType": "Type de Controleur", - "ControllerSettingsControllerTypeHandheld": "Portable", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", - "ControllerSettingsProfile": "Profil", - "ControllerSettingsProfileDefault": "Défaut", - "ControllerSettingsLoad": "Charger", - "ControllerSettingsAdd": "Ajouter", - "ControllerSettingsRemove": "Supprimer", - "ControllerSettingsButtons": "Boutons", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Croix Directionnelle", - "ControllerSettingsDPadUp": "Haut", - "ControllerSettingsDPadDown": "Bas", - "ControllerSettingsDPadLeft": "Gauche", - "ControllerSettingsDPadRight": "Droite", - "ControllerSettingsStickButton": "Bouton", - "ControllerSettingsStickUp": "Haut", - "ControllerSettingsStickDown": "Bas", - "ControllerSettingsStickLeft": "Gauche", - "ControllerSettingsStickRight": "Droite", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", - "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", - "ControllerSettingsStickDeadzone": "Zone morte :", - "ControllerSettingsLStick": "Joystick Gauche", - "ControllerSettingsRStick": "Joystick Droit", - "ControllerSettingsTriggersLeft": "Gachettes Gauche", - "ControllerSettingsTriggersRight": "Gachettes Droite", - "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", - "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", - "ControllerSettingsTriggers": "Gachettes", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", - "ControllerSettingsExtraButtonsRight": "Boutons Droite", - "ControllerSettingsMisc": "Divers", - "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", - "ControllerSettingsMotion": "Mouvements", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", - "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", - "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", - "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", - "ControllerSettingsMotionServerHost": "Addresse du Server:", - "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", - "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", - "ControllerSettingsSave": "Enregistrer", - "ControllerSettingsClose": "Fermer", - "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", - "UserProfilesSaveProfileName": "Enregistrer le nom du profil", - "UserProfilesChangeProfileImage": "Changer l'image du profil", - "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", - "UserProfilesAddNewProfile": "Ajouter un nouveau profil", - "UserProfilesDelete": "Supprimer", - "UserProfilesClose": "Fermer", - "ProfileNameSelectionWatermark": "Choisir un pseudo", - "ProfileImageSelectionTitle": "Sélection de l'image du profil", - "ProfileImageSelectionHeader": "Choisir l'image du profil", - "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", - "ProfileImageSelectionImportImage": "Importer une image", - "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", - "InputDialogTitle": "Fenêtre d'entrée de texte", - "InputDialogOk": "OK", - "InputDialogCancel": "Annuler", - "InputDialogAddNewProfileTitle": "Choisir un nom de profil", - "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", - "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", - "AvatarChoose": "Choisir", - "AvatarSetBackgroundColor": "Choisir une couleur de fond", - "AvatarClose": "Fermer", - "ControllerSettingsLoadProfileToolTip": "Charger un profil", - "ControllerSettingsAddProfileToolTip": "Ajouter un profil", - "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", - "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", - "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", - "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", - "GameListContextMenuRunApplication": "Démarrer l'application", - "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", - "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", - "SettingsTabGeneralTheme": "Thème", - "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", - "SettingsTabGeneralThemeBaseStyle": "Style par défaut", - "SettingsTabGeneralThemeBaseStyleDark": "Sombre", - "SettingsTabGeneralThemeBaseStyleLight": "Lumière", - "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", - "ButtonBrowse": "Parcourir", - "ControllerSettingsConfigureGeneral": "Configurer", - "ControllerSettingsRumble": "Vibreur", - "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", - "ControllerSettingsRumbleWeakMultiplier": "Multiplicateur de vibrations faibles", - "DialogMessageSaveNotAvailableMessage": "Il n'y a aucune sauvegarde pour {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Voulez-vous créer une sauvegarde pour ce jeu ?", - "DialogConfirmationTitle": "Ryujinx - Confirmation", - "DialogUpdaterTitle": "Ryujinx - Mise à Jour", - "DialogErrorTitle": "Ryujinx - Erreur", - "DialogWarningTitle": "Ryujinx - Avertissement", - "DialogExitTitle": "Ryujinx - Quitter", - "DialogErrorMessage": "Ryujinx a rencontré une erreur", - "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", - "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", - "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", - "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", - "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", - "DialogNcaExtractionMessage": "Extraction de la section {0} depuis {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Extracteur de la section NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Échec de l'extraction. Le NCA principal n'était pas présent dans le fichier sélectionné.", - "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", - "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", - "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", - "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", - "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", - "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", - "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", - "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", - "DialogUpdaterCompleteMessage": "Mise à jour terminée !", - "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", - "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", - "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", - "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", - "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", - "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", - "DialogRestartRequiredMessage": "Redémarrage Requis", - "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", - "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", - "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", - "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", - "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", - "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", - "DialogInstallFileTypesErrorMessage": "Échec de l'installation des types de fichiers.", - "DialogUninstallFileTypesSuccessMessage": "Types de fichiers désinstallés avec succès!", - "DialogUninstallFileTypesErrorMessage": "Échec de la désinstallation des types de fichiers.", - "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", - "DialogControllerAppletTitle": "Controller Applet", - "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", - "DialogErrorAppletErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nPour plus d'informations sur la manière de corriger cette erreur, suivez notre Guide d'Installation.", - "DialogUserErrorDialogTitle": "Erreur Ryujinx ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "Une erreur est survenue lors de la récupération des informations de l'API.", - "DialogAmiiboApiConnectErrorMessage": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.", - "DialogProfileInvalidProfileErrorMessage": "Le profil {0} est incompatible avec le système de configuration de manette actuel.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Le profil par défaut ne peut pas être écrasé", - "DialogProfileDeleteProfileTitle": "Supprimer le profil", - "DialogProfileDeleteProfileMessage": "Cette action est irréversible, êtes-vous sûr de vouloir continuer ?", - "DialogWarning": "Avertissement", - "DialogPPTCDeletionMessage": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", - "DialogPPTCDeletionErrorMessage": "Erreur lors de la purge du cache PPTC à {0}: {1}", - "DialogShaderDeletionMessage": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", - "DialogShaderDeletionErrorMessage": "Erreur lors de la purge du cache du Shader à {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx a rencontré une erreur", - "DialogInvalidTitleIdErrorMessage": "Erreur d'UI : le jeu sélectionné n'a pas d'ID de titre valide", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware valide n'a pas été trouvé dans {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Installer le Firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "La version {0} du système sera installée.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nCela remplacera la version actuelle du système {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVoulez-vous continuer ?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installation du firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Version du système {0} installée avec succès.", - "DialogUserProfileDeletionWarningMessage": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé", - "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", - "DialogUserProfileUnsavedChangesTitle": "Avertissement - Modifications non enregistrées", - "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", - "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", - "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", - "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", - "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", - "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Vous avez activé l'extraction des shaders, qui est conçu pour être utilisé par les développeurs uniquement.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver l'extraction des shaders. Souhaitez-vous désactiver l'extraction des shaders maintenant ?", - "DialogLoadAppGameAlreadyLoadedMessage": "Un jeu a déjà été chargé", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Veuillez arrêter l'émulation ou fermer l'émulateur avant de lancer un autre jeu.", - "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", - "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", - "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", - "CommonAuto": "Auto", - "CommonOff": "Désactivé", - "CommonOn": "Activé", - "InputDialogYes": "Oui", - "InputDialogNo": "Non", - "DialogProfileInvalidProfileNameErrorMessage": "Le nom du fichier contient des caractères invalides. Veuillez réessayer.", - "MenuBarOptionsPauseEmulation": "Suspendre", - "MenuBarOptionsResumeEmulation": "Reprendre", - "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", - "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", - "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", - "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", - "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", - "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", - "AboutRyujinxAboutTitle": "Á propos:", - "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", - "AboutRyujinxMaintainersTitle": "Maintenu par :", - "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", - "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", - "AmiiboSeriesLabel": "Séries Amiibo", - "AmiiboCharacterLabel": "Personnage", - "AmiiboScanButtonLabel": "Scanner", - "AmiiboOptionsShowAllLabel": "Afficher tous les Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack : Utiliser un tag Uuid aléatoire", - "DlcManagerTableHeadingEnabledLabel": "Activé", - "DlcManagerTableHeadingTitleIdLabel": "ID du titre", - "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", - "DlcManagerTableHeadingFullPathLabel": "Chemin complet", - "DlcManagerRemoveAllButton": "Tout supprimer", - "DlcManagerEnableAllButton": "Activer Tout", - "DlcManagerDisableAllButton": "Désactiver Tout", - "MenuBarOptionsChangeLanguage": "Changer la Langue", - "MenuBarShowFileTypes": "Afficher les types de fichiers", - "CommonSort": "Trier", - "CommonShowNames": "Afficher les noms", - "CommonFavorite": "Favoris", - "OrderAscending": "Croissant", - "OrderDescending": "Décroissant", - "SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations", - "ErrorWindowTitle": "Fenêtre d'erreur", - "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", - "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", - "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", - "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", - "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", - "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", - "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", - "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", - "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Fournit aux jeux l'accès à votre clavier en tant que périphérique de saisie de texte.", - "DirectMouseTooltip": "Prise en charge de l'accès à la souris (HID). Permet aux jeux d'accéder a votre souris en tant que périphérique de pointage.", - "RegionTooltip": "Changer la région du système", - "LanguageTooltip": "Changer la langue du système", - "TimezoneTooltip": "Changer le fuseau horaire du système", - "TimeTooltip": "Changer l'heure du système", - "VSyncToggleTooltip": "Synchronisation verticale de l'émulateur. Généralement un limiteur d'FPS pour la majorité des jeux, le désactiver peut accélérer les jeux pour rendre les écrans de chargement plus court, mais augemente le risque de crash lors des chargements.\n\nPeut être activer ou desactiver en jeu avec un raccourci de votre choix. Nous vous recommandons de le laisser.\n\nLaissez activer, si vous n'êtes pas sûr.", - "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", - "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", - "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", - "MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.", - "MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.", - "MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.", - "MemoryManagerUnsafeTooltip": "Mapper directement la mémoire dans la carte, mais ne pas masquer l'adresse dans l'espace d'adressage du client avant l'accès. Plus rapide, mais la sécurité sera négliger. L'application peut accéder à la mémoire depuis n'importe où dans Ryujinx, donc exécutez uniquement les programmes en qui vous avez confiance avec ce mode.", - "UseHypervisorTooltip": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsqu'il est disponible, mais peut être instable dans son état actuel.", - "DRamTooltip": "Utilise une disposition alternative de la mémoire pour imiter le kit de développeur de la Switch.\n\nActiver cette option uniquement pour les packs de textures 4k ou les mods à résolution 4k.\nN'améliore pas les performances, cause des crashs dans certains jeux si activer.\n\nLaissez Désactiver en cas d'incertitude.", - "IgnoreMissingServicesTooltip": "Ignore les services Horizon OS non-intégré. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nActivez-le en cas d'incertitude.", - "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", - "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", - "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", - "ResolutionScaleTooltip": "Échelle de résolution appliquer au rendu du jeu", - "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", - "AnisotropyTooltip": "Niveau de Filtrage Anisotropique (mettre sur Auto pour utiliser la valeur demandée par le jeu)", - "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", - "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", - "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", - "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", - "InfoLogTooltip": "Affiche les messages de log d'informations dans la console. N'affecte pas les performances.", - "WarnLogTooltip": "Affiche les messages d'avertissement dans la console. N'affecte pas les performances.", - "ErrorLogTooltip": "Affiche les messages de log d'erreur dans la console. N'affecte pas les performances.", - "TraceLogTooltip": "Affiche la trace des messages de log dans la console. N'affecte pas les performances.", - "GuestLogTooltip": "Affiche les messages de log des invités dans la console. N'affecte pas les performances.", - "FileAccessLogTooltip": "Affiche les messages de log d'accès aux fichiers dans la console.", - "FSAccessLogModeTooltip": "Active la sortie du journal d'accès FS de la console. Les modes possibles sont 0-3", - "DeveloperOptionTooltip": "Utiliser avec précaution", - "OpenGlLogLevel": "Nécessite l'activation des niveaux de journalisation appropriés", - "DebugLogTooltip": "Affiche les messages de débogage dans la console.\n\nN'utilisez ceci que si un membre du personnel le demande, car cela rendra les logs difficiles à lire et réduit les performances de l'émulateur.", - "LoadApplicationFileTooltip": "Ouvrir un explorateur de fichiers pour choisir un fichier compatible Switch à charger", - "LoadApplicationFolderTooltip": "Ouvrir un explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger", - "OpenRyujinxFolderTooltip": "Ouvrir le dossier du système de fichiers Ryujinx", - "OpenRyujinxLogsTooltip": "Ouvre le dossier dans lequel les journaux sont écrits", - "ExitTooltip": "Quitter Ryujinx", - "OpenSettingsTooltip": "Ouvrir la fenêtre de configuration", - "OpenProfileManagerTooltip": "Ouvrir la fenêtre du gestionnaire de profils d'utilisateurs", - "StopEmulationTooltip": "Arrêter l'émulation du jeu en cours et revenir à la sélection des jeux", - "CheckUpdatesTooltip": "Vérifier les mises à jour de Ryujinx", - "OpenAboutTooltip": "Ouvrir la fenêtre À Propos", - "GridSize": "Taille de la grille", - "GridSizeTooltip": "Modifier la taille des éléments de la grille", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugais brésilien", - "AboutRyujinxContributorsButtonHeader": "Voir tous les contributeurs", - "SettingsTabSystemAudioVolume": "Volume :", - "AudioVolumeTooltip": "Modifier le volume audio", - "SettingsTabSystemEnableInternetAccess": "Accès Internet Invité/Mode LAN", - "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", - "GameListContextMenuManageCheatToolTip": "Gérer la triche", - "GameListContextMenuManageCheat": "Gérer la triche", - "ControllerSettingsStickRange": "Intervalle:", - "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", - "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Audio", - "SettingsTabNetwork": "Réseau", - "SettingsTabNetworkConnection": "Connexion réseau", - "SettingsTabCpuCache": "Cache CPU", - "SettingsTabCpuMemory": "Mémoire CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", - "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", - "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", - "ControllerSettingsRotate90": "Rotation 90° horaire", - "IconSize": "Taille d'icône", - "IconSizeTooltip": "Changer la taille des icônes de jeu", - "MenuBarOptionsShowConsole": "Afficher la console", - "ShaderCachePurgeError": "Erreur lors de la purge du cache du Shader à {0}: {1}", - "UserErrorNoKeys": "Clés introuvables", - "UserErrorNoFirmware": "Firmware introuvable", - "UserErrorFirmwareParsingFailed": "Erreur d'analyse du firmware", - "UserErrorApplicationNotFound": " Application introuvable", - "UserErrorUnknown": "Erreur inconnue", - "UserErrorUndefined": "Erreur non définie", - "UserErrorNoKeysDescription": "Ryujinx n'a pas pu trouver votre fichier 'prod.keys'", - "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", - "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", - "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", - "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", - "OpenSetupGuideMessage": "Ouvrir le guide d'installation", - "NoUpdate": "Aucune mise à jour", - "TitleUpdateVersionLabel": "Version {0} - {1}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Confirmation", - "FileDialogAllTypes": "Tous les types", - "Never": "Jamais", - "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", - "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", - "SoftwareKeyboard": "Clavier logiciel", - "SoftwareKeyboardModeNumbersOnly": "Doit être uniquement des chiffres", - "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", - "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", - "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", - "UpdaterRenaming": "Renommage des anciens fichiers...", - "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", - "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", - "UpdaterExtracting": "Extraction de la mise à jour…", - "UpdaterDownloading": "Téléchargement de la mise à jour...", - "Game": "Jeu", - "Docked": "Attaché", - "Handheld": "Portable", - "ConnectionError": "Erreur de connexion.", - "AboutPageDeveloperListMore": "{0} et plus...", - "ApiError": "Erreur API.", - "LoadingHeading": "Chargement {0}", - "CompilingPPTC": "Compilation PTC", - "CompilingShaders": "Compilation des Shaders", - "AllKeyboards": "Tous les claviers", - "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", - "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", - "AllSupportedFormats": "Tous les formats supportés", - "RyujinxUpdater": "Mise à jour de Ryujinx", - "SettingsTabHotkeys": "Raccourcis clavier", - "SettingsTabHotkeysHotkeys": "Raccourcis clavier", - "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", - "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", - "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", - "SettingsTabHotkeysPauseHotkey": "Suspendre :", - "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", - "ControllerMotionTitle": "Réglages du contrôle par mouvement", - "ControllerRumbleTitle": "Paramètres de Vibration", - "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", - "SettingsXamlThemeFile": "Fichier thème Xaml", - "AvatarWindowTitle": "Gérer les Comptes - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Inconnu", - "Usage": "Utilisation", - "Writable": "Ecriture possible", - "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", - "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", - "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", - "CheatWindowTitle": "Gestionnaire de triches", - "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", - "UpdateWindowTitle": "Gestionnaire de mises à jour", - "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", - "UserProfilesEditProfile": "Éditer la sélection", - "Cancel": "Annuler", - "Save": "Enregistrer", - "Discard": "Abandonner", - "UserProfilesSetProfileImage": "Modifier l'image du profil", - "UserProfileEmptyNameError": "Le nom est requis", - "UserProfileNoImageError": "L'image du profil doit être définie", - "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", - "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", - "UserProfilesName": "Nom :", - "UserProfilesUserId": "Identifiant de l'utilisateur :", - "SettingsTabGraphicsBackend": "API de Rendu", - "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", - "SettingsEnableTextureRecompression": "Activer la recompression des textures", - "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", - "SettingsTabGraphicsPreferredGpu": "GPU préféré", - "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", - "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", - "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", - "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", - "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", - "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", - "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", - "SettingsEnableMacroHLE": "Activer les macros HLE", - "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", - "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", - "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans haut de gamme, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", - "VolumeShort": "Vol", - "UserProfilesManageSaves": "Gérer les sauvegardes", - "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", - "IrreversibleActionNote": "Cette action n'est pas réversible.", - "SaveManagerHeading": "Gérer les sauvegardes pour {0}", - "SaveManagerTitle": "Gestionnaire de sauvegarde", - "Name": "Nom ", - "Size": "Taille", - "Search": "Rechercher", - "UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus", - "Recover": "Récupérer", - "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", - "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", - "GraphicsAATooltip": "Applique l'anticrénelage au rendu du jeu", - "GraphicsAALabel": "Anticrénelage :", - "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", - "GraphicsScalingFilterTooltip": "Active la mise à l'échelle du tampon d'image", - "GraphicsScalingFilterLevelLabel": "Niveau ", - "GraphicsScalingFilterLevelTooltip": "Définir le niveau du filtre de mise à l'échelle", - "SmaaLow": "SMAA Faible", - "SmaaMedium": "SMAA moyen", - "SmaaHigh": "SMAA Élevé", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Modifier Utilisateur", - "UserEditorTitleCreate": "Créer Utilisateur", - "SettingsTabNetworkInterface": "Interface Réseau :", - "NetworkInterfaceTooltip": "Interface réseau pour les fonctionnalités LAN", - "NetworkInterfaceDefault": "Par défaut", - "PackagingShaders": "Empaquetage des Shaders", - "AboutChangelogButton": "Voir le Changelog sur GitHub", - "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/he_IL.json b/src/Ryujinx.Ava/Assets/Locales/he_IL.json deleted file mode 100644 index e5caf445..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/he_IL.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "אנגלית (ארה\"ב)", - "MenuBarFileOpenApplet": "פתח יישומון", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", - "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", - "SettingsTabSystemMemoryManagerMode": "מצב מנהל זיכרון:", - "SettingsTabSystemMemoryManagerModeSoftware": "תוכנה", - "SettingsTabSystemMemoryManagerModeHost": "מארח (מהיר)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "מארח לא מבוקר (המהיר ביותר, לא בטוח)", - "SettingsTabSystemUseHypervisor": "השתמש ב Hypervisor", - "MenuBarFile": "_קובץ", - "MenuBarFileOpenFromFile": "_טען יישום מקובץ", - "MenuBarFileOpenUnpacked": "טען משחק _שאינו ארוז", - "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", - "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", - "MenuBarFileExit": "_יציאה", - "MenuBarOptions": "הגדרות", - "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", - "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", - "MenuBarOptionsStopEmulation": "עצור אמולציה", - "MenuBarOptionsSettings": "_הגדרות", - "MenuBarOptionsManageUserProfiles": "_נהל פרופילי משתמש", - "MenuBarActions": "_פעולות", - "MenuBarOptionsSimulateWakeUpMessage": "דמה הודעת השכמה", - "MenuBarActionsScanAmiibo": "סרוק אמיבו", - "MenuBarTools": "_כלים", - "MenuBarToolsInstallFirmware": "התקן קושחה", - "MenuBarFileToolsInstallFirmwareFromFile": "התקן קושחה מקובץ- ZIP/XCI", - "MenuBarFileToolsInstallFirmwareFromDirectory": "התקן קושחה מתוך תקייה", - "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", - "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", - "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", - "MenuBarHelp": "עזרה", - "MenuBarHelpCheckForUpdates": "חפש עדכונים", - "MenuBarHelpAbout": "אודות", - "MenuSearch": "חפש...", - "GameListHeaderFavorite": "אהוב", - "GameListHeaderIcon": "סמל", - "GameListHeaderApplication": "שם", - "GameListHeaderDeveloper": "מפתח", - "GameListHeaderVersion": "גרסה", - "GameListHeaderTimePlayed": "זמן משחק", - "GameListHeaderLastPlayed": "שוחק לאחרונה", - "GameListHeaderFileExtension": "סיומת קובץ", - "GameListHeaderFileSize": "גודל הקובץ", - "GameListHeaderPath": "נתיב", - "GameListContextMenuOpenUserSaveDirectory": "פתח את תקיית השמור של המשתמש", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "פותח את תקיית השמור של המשתמש ביישום הנוכחי", - "GameListContextMenuOpenDeviceSaveDirectory": "פתח את תקיית השמור של המכשיר", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "פותח את הספרייה המכילה את שמור המכשיר של היישום", - "GameListContextMenuOpenBcatSaveDirectory": "פתח את תקיית השמור של ה-BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "פותח את תקיית שמור ה-BCAT של היישום", - "GameListContextMenuManageTitleUpdates": "מנהל עדכוני משחקים", - "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", - "GameListContextMenuManageDlc": "מנהל הרחבות", - "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", - "GameListContextMenuOpenModsDirectory": "פתח את תקיית המודים", - "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התקייה המכילה את המודים של היישום", - "GameListContextMenuCacheManagement": "ניהול מטמון", - "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", - "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", - "GameListContextMenuCacheManagementPurgeShaderCache": "ניקוי מטמון הצללות", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "מוחק את מטמון ההצללות של היישום", - "GameListContextMenuCacheManagementOpenPptcDirectory": "פתח את תקיית PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "פותח את התקייה של מטמון ה-PPTC של האפליקציה", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "פתח את תקיית המטמון של ההצללות", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "פותח את תקיית מטמון ההצללות של היישום", - "GameListContextMenuExtractData": "חילוץ נתונים", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "חלץ את קטע ה-ExeFS מתצורת היישום הנוכחית (כולל עדכונים)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", - "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", - "StatusBarSystemVersion": "גרסת מערכת: {0}", - "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", - "LinuxVmMaxMapCountDialogTextPrimary": "האם תרצה להגביר את הערך של vm.max_map_count ל{0}", - "LinuxVmMaxMapCountDialogTextSecondary": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "כן, עד האתחול הבא", - "LinuxVmMaxMapCountDialogButtonPersistent": "כן, לצמיתות", - "LinuxVmMaxMapCountWarningTextPrimary": "הכמות המירבית של מיפויי הזיכרון נמוכה מהמומלץ.", - "LinuxVmMaxMapCountWarningTextSecondary": "הערך הנוכחי של vm.max_map_count {0} נמוך מ{1}. משחקים מסוימים עלולים לייצר עוד מיפוי זיכרון ממה שמתאפשר.Ryujinx יקרוס ברגע שהמגבלה תחרוג.\n\nיתכן ותרצה להעלות את המגבלה הנוכחית או להתקין את pkexec, אשר יאפשר לRyujinx לסייע בכך.", - "Settings": "הגדרות", - "SettingsTabGeneral": "ממשק משתמש", - "SettingsTabGeneralGeneral": "כללי", - "SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד", - "SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה", - "SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"", - "SettingsTabGeneralHideCursor": "הסתר את הסמן", - "SettingsTabGeneralHideCursorNever": "אף פעם", - "SettingsTabGeneralHideCursorOnIdle": "במצב סרק", - "SettingsTabGeneralHideCursorAlways": "תמיד", - "SettingsTabGeneralGameDirectories": "תקיות משחקים", - "SettingsTabGeneralAdd": "הוסף", - "SettingsTabGeneralRemove": "הסר", - "SettingsTabSystem": "מערכת", - "SettingsTabSystemCore": "ליבה", - "SettingsTabSystemSystemRegion": "אזור מערכת:", - "SettingsTabSystemSystemRegionJapan": "יפן", - "SettingsTabSystemSystemRegionUSA": "ארה\"ב", - "SettingsTabSystemSystemRegionEurope": "אירופה", - "SettingsTabSystemSystemRegionAustralia": "אוסטרליה", - "SettingsTabSystemSystemRegionChina": "סין", - "SettingsTabSystemSystemRegionKorea": "קוריאה", - "SettingsTabSystemSystemRegionTaiwan": "טייוואן", - "SettingsTabSystemSystemLanguage": "שפת המערכת:", - "SettingsTabSystemSystemLanguageJapanese": "יפנית", - "SettingsTabSystemSystemLanguageAmericanEnglish": "אנגלית אמריקאית", - "SettingsTabSystemSystemLanguageFrench": "צרפתית", - "SettingsTabSystemSystemLanguageGerman": "גרמנית", - "SettingsTabSystemSystemLanguageItalian": "איטלקית", - "SettingsTabSystemSystemLanguageSpanish": "ספרדית", - "SettingsTabSystemSystemLanguageChinese": "סינית", - "SettingsTabSystemSystemLanguageKorean": "קוריאנית", - "SettingsTabSystemSystemLanguageDutch": "הולנדית", - "SettingsTabSystemSystemLanguagePortuguese": "פורטוגזית", - "SettingsTabSystemSystemLanguageRussian": "רוסית", - "SettingsTabSystemSystemLanguageTaiwanese": "טייוואנית", - "SettingsTabSystemSystemLanguageBritishEnglish": "אנגלית בריטית", - "SettingsTabSystemSystemLanguageCanadianFrench": "צרפתית קנדית", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "ספרדית אמריקה הלטינית", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "סינית פשוטה", - "SettingsTabSystemSystemLanguageTraditionalChinese": "סינית מסורתית", - "SettingsTabSystemSystemTimeZone": "אזור זמן מערכת:", - "SettingsTabSystemSystemTime": "זמן מערכת:", - "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "FS בדיקות תקינות", - "SettingsTabSystemAudioBackend": "אחראי שמע:", - "SettingsTabSystemAudioBackendDummy": "גולם", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "האצות", - "SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות", - "SettingsTabSystemExpandDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)", - "SettingsTabSystemIgnoreMissingServices": "התעלם משירותים חסרים", - "SettingsTabGraphics": "גרפיקה", - "SettingsTabGraphicsAPI": "ממשק גראפי", - "SettingsTabGraphicsEnableShaderCache": "הפעל מטמון הצללות", - "SettingsTabGraphicsAnisotropicFiltering": "סינון אניסוטרופי:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "אוטומטי", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "קנה מידה של רזולוציה:", - "SettingsTabGraphicsResolutionScaleCustom": "מותאם אישית (לא מומלץ)", - "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "מתח לגודל חלון", - "SettingsTabGraphicsDeveloperOptions": "אפשרויות מפתח", - "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", - "SettingsTabLogging": "רישום", - "SettingsTabLoggingLogging": "רישום", - "SettingsTabLoggingEnableLoggingToFile": "אפשר רישום לקובץ", - "SettingsTabLoggingEnableStubLogs": "אפשר רישום בדל", - "SettingsTabLoggingEnableInfoLogs": "אפשר רישום מידע", - "SettingsTabLoggingEnableWarningLogs": "אפשר רישום אזהרות", - "SettingsTabLoggingEnableErrorLogs": "אפשר רישום שגיאות", - "SettingsTabLoggingEnableTraceLogs": "הפעל רישום מעקבי", - "SettingsTabLoggingEnableGuestLogs": "הפעל רישום מארח", - "SettingsTabLoggingEnableFsAccessLogs": "אפשר רישום גישת קבצי מערכת", - "SettingsTabLoggingFsGlobalAccessLogMode": "מצב רישום גלובלי של גישת קבצי מערכת", - "SettingsTabLoggingDeveloperOptions": "אפשרויות מפתח", - "SettingsTabLoggingDeveloperOptionsNote": "אזהרה: יפחית ביצועים", - "SettingsTabLoggingGraphicsBackendLogLevel": "רישום גרפיקת קצה אחורי:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "כלום", - "SettingsTabLoggingGraphicsBackendLogLevelError": "שגיאה", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "האטות", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "הכל", - "SettingsTabLoggingEnableDebugLogs": "אפשר רישום ניפוי באגים", - "SettingsTabInput": "קלט", - "SettingsTabInputEnableDockedMode": "מצב עגינה", - "SettingsTabInputDirectKeyboardAccess": "גישה ישירה למקלדת", - "SettingsButtonSave": "שמירה", - "SettingsButtonClose": "סגירה", - "SettingsButtonOk": "אישור", - "SettingsButtonCancel": "ביטול", - "SettingsButtonApply": "החל", - "ControllerSettingsPlayer": "שחקן/ית", - "ControllerSettingsPlayer1": "שחקן/ית 1", - "ControllerSettingsPlayer2": "שחקן/ית 2", - "ControllerSettingsPlayer3": "שחקן/ית 3", - "ControllerSettingsPlayer4": "שחקן/ית 4", - "ControllerSettingsPlayer5": "שחקן/ית 5", - "ControllerSettingsPlayer6": "שחקן/ית 6", - "ControllerSettingsPlayer7": "שחקן/ית 7", - "ControllerSettingsPlayer8": "שחקן/ית 8", - "ControllerSettingsHandheld": "נייד", - "ControllerSettingsInputDevice": "מכשיר קלט", - "ControllerSettingsRefresh": "רענון", - "ControllerSettingsDeviceDisabled": "מושבת", - "ControllerSettingsControllerType": "סוג שלט", - "ControllerSettingsControllerTypeHandheld": "נייד", - "ControllerSettingsControllerTypeProController": "שלט פרו ", - "ControllerSettingsControllerTypeJoyConPair": "ג'ויקון הותאם", - "ControllerSettingsControllerTypeJoyConLeft": "ג'ויקון שמאלי ", - "ControllerSettingsControllerTypeJoyConRight": "ג'ויקון ימני", - "ControllerSettingsProfile": "פרופיל", - "ControllerSettingsProfileDefault": "ברירת המחדל", - "ControllerSettingsLoad": "טעינה", - "ControllerSettingsAdd": "הוספה", - "ControllerSettingsRemove": "הסר", - "ControllerSettingsButtons": "כפתורים", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "כפתורי כיוונים", - "ControllerSettingsDPadUp": "מעלה", - "ControllerSettingsDPadDown": "מטה", - "ControllerSettingsDPadLeft": "שמאלה", - "ControllerSettingsDPadRight": "ימינה", - "ControllerSettingsStickButton": "כפתור", - "ControllerSettingsStickUp": "למעלה", - "ControllerSettingsStickDown": "למטה", - "ControllerSettingsStickLeft": "שמאלה", - "ControllerSettingsStickRight": "ימינה", - "ControllerSettingsStickStick": "סטיק", - "ControllerSettingsStickInvertXAxis": "הפיכת הX של הסטיק", - "ControllerSettingsStickInvertYAxis": "הפיכת הY של הסטיק", - "ControllerSettingsStickDeadzone": "שטח מת:", - "ControllerSettingsLStick": "מקל שמאלי", - "ControllerSettingsRStick": "מקל ימני", - "ControllerSettingsTriggersLeft": "הדק שמאלי", - "ControllerSettingsTriggersRight": "הדק ימני", - "ControllerSettingsTriggersButtonsLeft": "כפתור הדק שמאלי", - "ControllerSettingsTriggersButtonsRight": "כפתור הדק ימני", - "ControllerSettingsTriggers": "הדקים", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "כפתורים משמאל", - "ControllerSettingsExtraButtonsRight": "כפתורים מימין", - "ControllerSettingsMisc": "שונות", - "ControllerSettingsTriggerThreshold": "סף הדק:", - "ControllerSettingsMotion": "תנועה", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "השתמש בתנועת CemuHook תואמת ", - "ControllerSettingsMotionControllerSlot": "מיקום שלט", - "ControllerSettingsMotionMirrorInput": "קלט מראה", - "ControllerSettingsMotionRightJoyConSlot": "מיקום ג'ויקון ימני", - "ControllerSettingsMotionServerHost": "מארח השרת:", - "ControllerSettingsMotionGyroSensitivity": "רגישות ג'ירוסקופ:", - "ControllerSettingsMotionGyroDeadzone": "שטח מת של הג'ירוסקופ:", - "ControllerSettingsSave": "שמירה", - "ControllerSettingsClose": "סגירה", - "UserProfilesSelectedUserProfile": "פרופיל המשתמש הנבחר:", - "UserProfilesSaveProfileName": "שמור שם פרופיל", - "UserProfilesChangeProfileImage": "שנה תמונת פרופיל", - "UserProfilesAvailableUserProfiles": "פרופילי משתמש זמינים:", - "UserProfilesAddNewProfile": "צור פרופיל", - "UserProfilesDelete": "מחיקה", - "UserProfilesClose": "סגור", - "ProfileNameSelectionWatermark": "בחרו כינוי", - "ProfileImageSelectionTitle": "בחירת תמונת פרופיל", - "ProfileImageSelectionHeader": "בחרו תמונת פרופיל", - "ProfileImageSelectionNote": "אתם יכולים לייבא תמונת פרופיל מותאמת אישית, או לבחור אווטאר מקושחת המערכת", - "ProfileImageSelectionImportImage": "ייבוא קובץ תמונה", - "ProfileImageSelectionSelectAvatar": "בחרו אוואטר קושחה", - "InputDialogTitle": "דיאלוג קלט", - "InputDialogOk": "בסדר", - "InputDialogCancel": "ביטול", - "InputDialogAddNewProfileTitle": "בחרו את שם הפרופיל", - "InputDialogAddNewProfileHeader": "אנא הזינו שם לפרופיל", - "InputDialogAddNewProfileSubtext": "(אורך מרבי: {0})", - "AvatarChoose": "בחרו דמות", - "AvatarSetBackgroundColor": "הגדר צבע רקע", - "AvatarClose": "סגור", - "ControllerSettingsLoadProfileToolTip": "טען פרופיל", - "ControllerSettingsAddProfileToolTip": "הוסף פרופיל", - "ControllerSettingsRemoveProfileToolTip": "הסר פרופיל", - "ControllerSettingsSaveProfileToolTip": "שמור פרופיל", - "MenuBarFileToolsTakeScreenshot": "צלם מסך", - "MenuBarFileToolsHideUi": "הסתר ממשק משתמש ", - "GameListContextMenuRunApplication": "הרץ יישום", - "GameListContextMenuToggleFavorite": "למתג העדפה", - "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", - "SettingsTabGeneralTheme": "ערכת נושא", - "SettingsTabGeneralThemeCustomTheme": "נתיב ערכת נושא מותאמת אישית", - "SettingsTabGeneralThemeBaseStyle": "סגנון בסיס", - "SettingsTabGeneralThemeBaseStyleDark": "כהה", - "SettingsTabGeneralThemeBaseStyleLight": "בהיר", - "SettingsTabGeneralThemeEnableCustomTheme": "אפשר ערכת נושא מותאמת אישית", - "ButtonBrowse": "עיין", - "ControllerSettingsConfigureGeneral": "הגדר", - "ControllerSettingsRumble": "רטט", - "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", - "ControllerSettingsRumbleWeakMultiplier": "מכפיל רטט חלש", - "DialogMessageSaveNotAvailableMessage": "אין שמור משחק עבור [{1:x16}] {0}", - "DialogMessageSaveNotAvailableCreateSaveMessage": "האם תרצה ליצור שמור משחק עבור המשחק הזה?", - "DialogConfirmationTitle": "ריוג'ינקס - אישור", - "DialogUpdaterTitle": "ריוג'ינקס - מעדכן", - "DialogErrorTitle": "ריוג'ינקס - שגיאה", - "DialogWarningTitle": "ריוג'ינקס - אזהרה", - "DialogExitTitle": "ריוג'ינקס - יציאה", - "DialogErrorMessage": "ריוג'ינקס נתקל בשגיאה", - "DialogExitMessage": "האם אתם בטוחים שאתם רוצים לסגור את ריוג'ינקס?", - "DialogExitSubMessage": "כל הנתונים שלא נשמרו יאבדו!", - "DialogMessageCreateSaveErrorMessage": "אירעה שגיאה ביצירת שמור המשחק שצויין: {0}", - "DialogMessageFindSaveErrorMessage": "אירעה שגיאה במציאת שמור המשחק שצויין: {0}", - "FolderDialogExtractTitle": "בחרו את התיקייה לחילוץ", - "DialogNcaExtractionMessage": "מלחץ {0} ממקטע {1}...", - "DialogNcaExtractionTitle": "ריוג'ינקס - מחלץ מקטע NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "כשל בחילוץ. ה-NCA הראשי לא היה קיים בקובץ שנבחר.", - "DialogNcaExtractionCheckLogErrorMessage": "כשל בחילוץ. קרא את קובץ הרישום למידע נוסף.", - "DialogNcaExtractionSuccessMessage": "החילוץ הושלם בהצלחה.", - "DialogUpdaterConvertFailedMessage": "המרת הגרסה הנוכחית של ריוג'ינקס נכשלה.", - "DialogUpdaterCancelUpdateMessage": "מבטל עדכון!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "אתם כבר משתמשים בגרסה המעודכנת ביותר של ריוג'ינקס!", - "DialogUpdaterFailedToGetVersionMessage": "אירעה שגיאה בעת ניסיון לקבל עדכונים מ-גיטהב. זה יכול להיגרם אם הגרסה המעודכנת האחרונה נוצרה על ידי פעולות של גיטהב. נסה שוב בעוד מספר דקות.", - "DialogUpdaterConvertFailedGithubMessage": "המרת גרסת ריוג'ינקס שהתקבלה מ-עדכון הגרסאות של גיטהב נכשלה.", - "DialogUpdaterDownloadingMessage": "מוריד עדכון...", - "DialogUpdaterExtractionMessage": "מחלץ עדכון...", - "DialogUpdaterRenamingMessage": "משנה את שם העדכון...", - "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", - "DialogUpdaterCompleteMessage": "העדכון הושלם!", - "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", - "DialogUpdaterArchNotSupportedMessage": "אינך מריץ ארכיטקטורת מערכת נתמכת!", - "DialogUpdaterArchNotSupportedSubMessage": "(רק מערכות x64 נתמכות!)", - "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", - "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", - "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", - "DialogUpdaterDirtyBuildSubMessage": "אם אתם מחפשים גרסא נתמכת, אנא הורידו את ריוג'ינקס בכתובת https://ryujinx.org", - "DialogRestartRequiredMessage": "אתחול נדרש", - "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", - "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", - "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \\nהאמולטור יופעל כעת.", - "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", - "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", - "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", - "DialogInstallFileTypesErrorMessage": "נכשל בהתקנת סוגי קבצים.", - "DialogUninstallFileTypesSuccessMessage": "סוגי קבצים הוסרו בהצלחה!", - "DialogUninstallFileTypesErrorMessage": "נכשל בהסרת סוגי קבצים.", - "DialogOpenSettingsWindowLabel": "פתח את חלון ההגדרות", - "DialogControllerAppletTitle": "יישומון בקר", - "DialogMessageDialogErrorExceptionMessage": "שגיאה בהצגת דיאלוג ההודעה: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "שגיאה בהצגת תוכנת המקלדת: {0}", - "DialogErrorAppletErrorExceptionMessage": "שגיאה בהצגת דיאלוג ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nלמידע נוסף על איך לתקן שגיאה זו, עקוב אחר מדריך ההתקנה שלנו.", - "DialogUserErrorDialogTitle": "שגיאת Ryujinx ({0})", - "DialogAmiiboApiTitle": "ממשק תכנות אמיבו", - "DialogAmiiboApiFailFetchMessage": "אירעה שגיאה בעת שליפת מידע מהממשק.", - "DialogAmiiboApiConnectErrorMessage": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.", - "DialogProfileInvalidProfileErrorMessage": "הפרופיל {0} אינו תואם למערכת תצורת הקלט הנוכחית.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "לא ניתן להחליף את פרופיל ברירת המחדל", - "DialogProfileDeleteProfileTitle": "מוחק פרופיל", - "DialogProfileDeleteProfileMessage": "פעולה זו היא בלתי הפיכה, האם אתם בטוחים שברצונכם להמשיך?", - "DialogWarning": "אזהרה", - "DialogPPTCDeletionMessage": "אם תמשיכו אתם עומדים לגרום לבנייה מחדש של מטמון ה-PPTC עבור:\n\n{0}", - "DialogPPTCDeletionErrorMessage": "שגיאה בטיהור מטמון PPTC ב-{0}: {1}", - "DialogShaderDeletionMessage": "אם תמשיכו אתם עומדים למחוק את מטמון ההצללות עבור:\n\n{0}", - "DialogShaderDeletionErrorMessage": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", - "DialogRyujinxErrorMessage": "ריוג'ינקס נתקלה בשגיאה", - "DialogInvalidTitleIdErrorMessage": "שגיאת ממשק משתמש: למשחק שנבחר לא קיים מזהה משחק", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "לא נמצאה קושחת מערכת תקפה ב-{0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "התקן קושחה {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "גירסת המערכת {0} תותקן.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nהאם ברצונך להמשיך?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "מתקין קושחה...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "גרסת המערכת {0} הותקנה בהצלחה.", - "DialogUserProfileDeletionWarningMessage": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק", - "DialogUserProfileDeletionConfirmMessage": "האם ברצונך למחוק את הפרופיל שנבחר", - "DialogUserProfileUnsavedChangesTitle": "אזהרה - שינויים לא שמורים", - "DialogUserProfileUnsavedChangesMessage": "ביצעת שינויים בפרופיל משתמש זה שלא נשמרו.", - "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", - "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", - "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", - "DialogLoadNcaErrorMessage": "{0}. קובץ שגוי: {1}", - "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", - "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "הפעלת השלכת הצללה, שנועדה לשמש מפתחים בלבד.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את השלכת הצללה. האם ברצונך להשבית את השלכת הצללה עכשיו?", - "DialogLoadAppGameAlreadyLoadedMessage": "ישנו משחק שכבר רץ כעת", - "DialogLoadAppGameAlreadyLoadedSubMessage": "אנא הפסק את האמולציה או סגור את האמולטור לפני הפעלת משחק אחר.", - "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", - "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", - "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", - "SettingsTabGraphicsFeaturesOptions": "אפשרויות", - "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", - "CommonAuto": "אוטומטי", - "CommonOff": "כבוי", - "CommonOn": "דלוק", - "InputDialogYes": "כן", - "InputDialogNo": "לא", - "DialogProfileInvalidProfileNameErrorMessage": "שם הקובץ מכיל תווים לא חוקיים. אנא נסה שוב.", - "MenuBarOptionsPauseEmulation": "הפסק", - "MenuBarOptionsResumeEmulation": "המשך", - "AboutUrlTooltipMessage": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.", - "AboutDisclaimerMessage": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.", - "AboutAmiiboDisclaimerMessage": "ממשק אמיבו (www.amiiboapi.com) משומש בהדמיית האמיבו שלנו.", - "AboutPatreonUrlTooltipMessage": "לחץ כדי לפתוח את דף הפטראון של ריוג'ינקס בדפדפן ברירת המחדל שלך.", - "AboutGithubUrlTooltipMessage": "לחץ כדי לפתוח את דף הגיטהב של ריוג'ינקס בדפדפן ברירת המחדל שלך.", - "AboutDiscordUrlTooltipMessage": "לחץ כדי לפתוח הזמנה לשרת הדיסקורד של ריוג'ינקס בדפדפן ברירת המחדל שלך.", - "AboutTwitterUrlTooltipMessage": "לחץ כדי לפתוח את דף הטוויטר של Ryujinx בדפדפן ברירת המחדל שלך.", - "AboutRyujinxAboutTitle": "אודות:", - "AboutRyujinxAboutContent": "ריוג'ינקס הוא אמולטור עבור הנינטנדו סוויץ' (כל הזכויות שמורות).\nבבקשה תתמכו בנו בפטראון.\nקבל את כל החדשות האחרונות בטוויטר או בדיסקורד שלנו.\nמפתחים המעוניינים לתרום יכולים לקבל מידע נוסף ב-גיטהאב או ב-דיסקורד שלנו.", - "AboutRyujinxMaintainersTitle": "מתוחזק על ידי:", - "AboutRyujinxMaintainersContentTooltipMessage": "לחץ כדי לפתוח את דף התורמים בדפדפן ברירת המחדל שלך.", - "AboutRyujinxSupprtersTitle": "תמוך באמצעות Patreon", - "AmiiboSeriesLabel": "סדרת אמיבו", - "AmiiboCharacterLabel": "דמות", - "AmiiboScanButtonLabel": "סרוק את זה", - "AmiiboOptionsShowAllLabel": "הצג את כל האמיבואים", - "AmiiboOptionsUsRandomTagLabel": "האצה: השתמש בתג Uuid אקראי", - "DlcManagerTableHeadingEnabledLabel": "מאופשר", - "DlcManagerTableHeadingTitleIdLabel": "מזהה משחק", - "DlcManagerTableHeadingContainerPathLabel": "נתיב מכיל", - "DlcManagerTableHeadingFullPathLabel": "נתיב מלא", - "DlcManagerRemoveAllButton": "מחק הכל", - "DlcManagerEnableAllButton": "אפשר הכל", - "DlcManagerDisableAllButton": "השבת הכל", - "MenuBarOptionsChangeLanguage": "החלף שפה", - "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", - "CommonSort": "מיין", - "CommonShowNames": "הצג שמות", - "CommonFavorite": "מועדף", - "OrderAscending": "סדר עולה", - "OrderDescending": "סדר יורד", - "SettingsTabGraphicsFeatures": "תכונות ושיפורים", - "ErrorWindowTitle": "חלון שגיאה", - "ToggleDiscordTooltip": "בחרו להציג את ריוג'ינקס או לא בפעילות הדיסקורד שלכם \"משוחק כרגע\".", - "AddGameDirBoxTooltip": "הזן תקיית משחקים כדי להוסיף לרשימה", - "AddGameDirTooltip": "הוסף תקיית משחקים לרשימה", - "RemoveGameDirTooltip": "הסר את תקיית המשחקים שנבחרה", - "CustomThemeCheckTooltip": "השתמש בעיצוב מותאם אישית של אבלוניה עבור ה-ממשק הגראפי כדי לשנות את המראה של תפריטי האמולטור", - "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", - "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", - "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", - "DirectKeyboardTooltip": "תמיכה ישירה למקלדת (HID). מספק גישה בשביל משחקים למקלדת שלך כמכשיר להזנת טקסט.", - "DirectMouseTooltip": "תמיכה ישירה לעכבר (HID). מספק גישה בשביל משחקים לעכבר שלך כהתקן הצבעה.", - "RegionTooltip": "שנה אזור מערכת", - "LanguageTooltip": "שנה שפת מערכת", - "TimezoneTooltip": "שנה את אזור הזמן של המערכת", - "TimeTooltip": "שנה זמן מערכת", - "VSyncToggleTooltip": "מדמה סנכרון אנכי של קונסולה. כלומר חסם הפריימים לרוב המשחקים; השבתה שלו עלולה לגרום למשחקים לרוץ מהר יותר או לגרום למסכי טעינה לקחת יותר זמן או להתקע.\n\nניתן לשנות מצב של תפריט זה בזמן משחק עם המקש לבחירתך. אנו ממליצים לעשות זאת אם אתם מתכננים להשבית אותו.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", - "MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.", - "MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.", - "MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.", - "MemoryManagerUnsafeTooltip": "ממפה זיכרון ישירות, אך לא ממסך את הכתובת בתוך כתובת המארח לפני הגישה. מהיר, אך במחיר של הגנה. יישום המארח בעל גישה לזיכרון מכל מקום בריוג'ינקס, לכן הריצו איתו רק קבצים שאתם סומכים עליהם.", - "UseHypervisorTooltip": "השתמש ב- Hypervisor במקום JIT. משפר מאוד ביצועים כשניתן, אבל יכול להיות לא יציב במצבו הנוכחי.", - "DRamTooltip": "מנצל תצורת מצב-זיכרון חלופית לחכות את מכשיר הפיתוח של הסוויץ'.\n\nזה שימושי להחלפת חבילות מרקמים באיכותיים יותר או כאלו ברזולוציית 4k. לא משפר ביצועים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", - "IgnoreMissingServicesTooltip": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", - "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", - "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", - "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "ResolutionScaleTooltip": "שיפור רזולוצייה המאופשרת לעיבוד מטרות.", - "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", - "AnisotropyTooltip": "רמת סינון אניסוטרופי (מוגדר לאוטומטי כדי להשתמש בערך המבוקש על ידי המשחק)", - "AspectRatioTooltip": "יחס גובה-רוחב הוחל על חלון המעבד.", - "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", - "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", - "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", - "InfoLogTooltip": "מדפיק רישומי מידע לשורת הפקודות. לא משפיע על ביצועי היישום.", - "WarnLogTooltip": "מדפיק רישומי הערות לשורת הפקודות. לא משפיע על ביצועי היישום.", - "ErrorLogTooltip": "מדפיס רישומי שגיאות לשורת הפקודות. לא משפיע על ביצועי היישום.", - "TraceLogTooltip": "מדפיק רישומי זיכרון לשורת הפקודות. לא משפיע על ביצועי היישום.", - "GuestLogTooltip": "מדפיס רישומי אורח לשורת הפקודות. לא משפיע על ביצועי היישום.", - "FileAccessLogTooltip": "מדפיס גישות לקבצי רישום לשורת הפקודות.", - "FSAccessLogModeTooltip": "מאפשר גישה לרישומי FS ליציאת שורת הפקודות. האפשרויות הינן 0-3.", - "DeveloperOptionTooltip": "השתמש בזהירות", - "OpenGlLogLevel": "דורש הפעלת רמות רישום מתאימות", - "DebugLogTooltip": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", - "LoadApplicationFileTooltip": "פתח סייר קבצים כדי לבחור קובץ תואם סוויץ' לטעינה", - "LoadApplicationFolderTooltip": "פתח סייר קבצים כדי לבחור יישום תואם סוויץ', לא ארוז לטעינה.", - "OpenRyujinxFolderTooltip": "פתח את תיקיית מערכת הקבצים ריוג'ינקס", - "OpenRyujinxLogsTooltip": "פותח את התיקיה שאליה נכתבים רישומים", - "ExitTooltip": "צא מריוג'ינקס", - "OpenSettingsTooltip": "פתח את חלון ההגדרות", - "OpenProfileManagerTooltip": "פתח את חלון מנהל פרופילי המשתמש", - "StopEmulationTooltip": "הפסק את הדמייה של המשחק הנוכחי וחזור למסך בחירת המשחק", - "CheckUpdatesTooltip": "בדוק אם קיימים עדכונים לריוג'ינקס", - "OpenAboutTooltip": "פתח את חלון אודות היישום", - "GridSize": "גודל רשת", - "GridSizeTooltip": "שנה את גודל המוצרים על הרשת.", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "פורטוגלית ברזילאית", - "AboutRyujinxContributorsButtonHeader": "צפה בכל התורמים", - "SettingsTabSystemAudioVolume": "עוצמת קול: ", - "AudioVolumeTooltip": "שנה עוצמת קול", - "SettingsTabSystemEnableInternetAccess": "אפשר גישה לאינטרנט בתור אורח/חיבור לאן", - "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", - "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", - "GameListContextMenuManageCheat": "נהל צ'יטים", - "ControllerSettingsStickRange": "טווח:", - "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", - "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", - "SettingsTabCpu": "מעבד", - "SettingsTabAudio": "שמע", - "SettingsTabNetwork": "רשת", - "SettingsTabNetworkConnection": "חיבור רשת", - "SettingsTabCpuCache": "מטמון מעבד", - "SettingsTabCpuMemory": "מצב מעבד", - "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", - "UpdaterDisabledWarningTitle": "מעדכן מושבת!", - "GameListContextMenuOpenSdModsDirectory": "פתח את תקיית המודים של אטמוספרה", - "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תקיית כרטיס ה-SD החלופית של אטמוספרה המכילה את המודים של האפליקציה. שימושי עבור מודים ארוזים עבור חומרה אמיתית.", - "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", - "IconSize": "גודל הסמל", - "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", - "MenuBarOptionsShowConsole": "הצג שורת פקודות", - "ShaderCachePurgeError": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", - "UserErrorNoKeys": "המפתחות לא נמצאו", - "UserErrorNoFirmware": "קושחה לא נמצאה", - "UserErrorFirmwareParsingFailed": "שגיאת ניתוח קושחה", - "UserErrorApplicationNotFound": "יישום לא נמצא", - "UserErrorUnknown": "שגיאה לא ידועה", - "UserErrorUndefined": "שגיאה לא מוגדרת", - "UserErrorNoKeysDescription": "ריוג'ינקס לא הצליח למצוא את קובץ ה-'prod.keys' שלך", - "UserErrorNoFirmwareDescription": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת", - "UserErrorFirmwareParsingFailedDescription": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.", - "UserErrorApplicationNotFoundDescription": "ריוג'ינקס לא מצאה יישום תקין בנתיב הנתון", - "UserErrorUnknownDescription": "קרתה שגיאה לא ידועה!", - "UserErrorUndefinedDescription": "קרתה שגיאה לא מוגדרת! זה לא אמור לקרות, אנא צרו קשר עם מפתח!", - "OpenSetupGuideMessage": "פתח מדריך התקנה", - "NoUpdate": "אין עדכון", - "TitleUpdateVersionLabel": "גרסה {0}", - "RyujinxInfo": "ריוג'ינקס - מידע", - "RyujinxConfirm": "ריוג'ינקס - אישור", - "FileDialogAllTypes": "כל הסוגים", - "Never": "אף פעם", - "SwkbdMinCharacters": "לפחות {0} תווים", - "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", - "SoftwareKeyboard": "מקלדת וירטואלית", - "SoftwareKeyboardModeNumbersOnly": "מחויב להיות מספרי בלבד", - "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", - "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", - "DialogControllerAppletMessagePlayerRange": "האפליקציה מבקשת {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletMessage": "האפליקציה מבקשת בדיוק {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletDockModeSet": "במצב עגינה. בנוסף מצב נייד לא אפשרי.\n\n", - "UpdaterRenaming": "משנה שמות של קבצים ישנים...", - "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", - "UpdaterAddingFiles": "מוסיף קבצים חדשים...", - "UpdaterExtracting": "מחלץ עדכון...", - "UpdaterDownloading": "מוריד עדכון...", - "Game": "משחק", - "Docked": "בתחנת עגינה", - "Handheld": "נייד", - "ConnectionError": "שגיאת חיבור", - "AboutPageDeveloperListMore": "{0} ועוד...", - "ApiError": "שגיאת ממשק.", - "LoadingHeading": "טוען {0}", - "CompilingPPTC": "קימפול PTC", - "CompilingShaders": "קימפול הצללות", - "AllKeyboards": "כל המקלדות", - "OpenFileDialogTitle": "בחר קובץ נתמך לפתיחה", - "OpenFolderDialogTitle": "בחר תיקיה עם משחק לא ארוז", - "AllSupportedFormats": "כל הפורמטים הנתמכים", - "RyujinxUpdater": "מעדכן ריוג'ינקס", - "SettingsTabHotkeys": "מקשי קיצור במקלדת", - "SettingsTabHotkeysHotkeys": "מקשי קיצור במקלדת", - "SettingsTabHotkeysToggleVsyncHotkey": "שנה סינכרון אנכי:", - "SettingsTabHotkeysScreenshotHotkey": "צילום מסך:", - "SettingsTabHotkeysShowUiHotkey": "הצג ממשק משתמש:", - "SettingsTabHotkeysPauseHotkey": "הפסק:", - "SettingsTabHotkeysToggleMuteHotkey": "השתק:", - "ControllerMotionTitle": "הגדרות שליטת תנועות ג'ירוסקופ", - "ControllerRumbleTitle": "הגדרות רטט", - "SettingsSelectThemeFileDialogTitle": "בחרו קובץ ערכת נושא", - "SettingsXamlThemeFile": "קובץ ערכת נושא Xaml", - "AvatarWindowTitle": "ניהול חשבונות - אוואטר", - "Amiibo": "אמיבו", - "Unknown": "לא ידוע", - "Usage": "שימוש", - "Writable": "ניתן לכתיבה", - "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", - "SelectUpdateDialogTitle": "בחרו קבצי עדכון", - "UserProfileWindowTitle": "ניהול פרופילי משתמש", - "CheatWindowTitle": "נהל צ'יטים למשחק", - "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", - "UpdateWindowTitle": "נהל עדכוני משחקים", - "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", - "BuildId": "מזהה בניה:", - "DlcWindowHeading": "{0} הרחבות משחק", - "UserProfilesEditProfile": "ערוך נבחר/ים", - "Cancel": "בטל", - "Save": "שמור", - "Discard": "השלך", - "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", - "UserProfileEmptyNameError": "נדרש שם", - "UserProfileNoImageError": "נדרשת תמונת פרופיל", - "GameUpdateWindowHeading": "נהל עדכונים עבור {0} ({1})", - "SettingsTabHotkeysResScaleUpHotkey": "שפר רזולוציה:", - "SettingsTabHotkeysResScaleDownHotkey": "הפחת רזולוציה:", - "UserProfilesName": "שם:", - "UserProfilesUserId": "מזהה משתמש:", - "SettingsTabGraphicsBackend": "אחראי גראפיקה", - "SettingsTabGraphicsBackendTooltip": "אחראי גראפיקה לשימוש", - "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", - "SettingsEnableTextureRecompressionTooltip": " דוחס מרקמים מסויימים להפחתת השימוש בראם הוירטואלי.\n\nמומלץ לשימוש עם כרטיס גראפי בעל פחות מ-4GiB בראם הוירטואלי.\n\nמוטב להשאיר כבוי אם אינכם בטוחים.", - "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", - "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", - "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", - "SettingsGpuBackendRestartMessage": "הגדרות אחראי גרפיקה או כרטיס גראפי שונו. זה ידרוש הפעלה מחדש כדי להחיל שינויים", - "SettingsGpuBackendRestartSubMessage": "האם ברצונך להפעיל מחדש כעט?", - "RyujinxUpdaterMessage": "האם ברצונך לעדכן את ריוג'ינקס לגרסא האחרונה?", - "SettingsTabHotkeysVolumeUpHotkey": "הגבר את עוצמת הקול:", - "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", - "SettingsEnableMacroHLE": "Enable Macro HLE", - "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "שמע", - "UserProfilesManageSaves": "נהל שמורים", - "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", - "IrreversibleActionNote": "הפעולה הזו בלתי הפיכה.", - "SaveManagerHeading": "נהל שמורי משחק עבור {0} ({1})", - "SaveManagerTitle": "מנהל שמירות", - "Name": "שם", - "Size": "גודל", - "Search": "חפש", - "UserProfilesRecoverLostAccounts": "שחזר חשבון שאבד", - "Recover": "שחזר", - "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", - "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", - "GraphicsAATooltip": "מחיל החלקת-עקומות על עיבוד המשחק", - "GraphicsAALabel": "החלקת-עקומות:", - "GraphicsScalingFilterLabel": "מסנן מידת איכות:", - "GraphicsScalingFilterTooltip": "אפשר מידת איכות מסוג איגור-תמונה", - "GraphicsScalingFilterLevelLabel": "רמה", - "GraphicsScalingFilterLevelTooltip": "קבע מידת איכות תמונה לפי רמת סינון", - "SmaaLow": "SMAA נמוך", - "SmaaMedium": "SMAA בינוני", - "SmaaHigh": "SMAA גבוהה", - "SmaaUltra": "SMAA אולטרה", - "UserEditorTitle": "ערוך משתמש", - "UserEditorTitleCreate": "צור משתמש", - "SettingsTabNetworkInterface": "ממשק רשת", - "NetworkInterfaceTooltip": "ממשק הרשת המשומש עבור יכולות לאן", - "NetworkInterfaceDefault": "ברירת המחדל", - "PackagingShaders": "אורז הצללות", - "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", - "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/it_IT.json b/src/Ryujinx.Ava/Assets/Locales/it_IT.json deleted file mode 100644 index 5aff7a7e..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/it_IT.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Italiano", - "MenuBarFileOpenApplet": "Apri Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", - "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", - "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", - "SettingsTabSystemMemoryManagerModeSoftware": "Software", - "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", - "SettingsTabSystemUseHypervisor": "Usa Hypervisor", - "MenuBarFile": "_File", - "MenuBarFileOpenFromFile": "_Carica applicazione da un file", - "MenuBarFileOpenUnpacked": "Carica _gioco estratto", - "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", - "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", - "MenuBarFileExit": "_Esci", - "MenuBarOptions": "Opzioni", - "MenuBarOptionsToggleFullscreen": "Schermo intero", - "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", - "MenuBarOptionsStopEmulation": "Ferma emulazione", - "MenuBarOptionsSettings": "_Impostazioni", - "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", - "MenuBarActions": "_Azioni", - "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", - "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", - "MenuBarTools": "_Strumenti", - "MenuBarToolsInstallFirmware": "Installa firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", - "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", - "MenuBarToolsInstallFileTypes": "Installa i tipi di file", - "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", - "MenuBarHelp": "Aiuto", - "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", - "MenuBarHelpAbout": "Informazioni", - "MenuSearch": "Cerca...", - "GameListHeaderFavorite": "Preferito", - "GameListHeaderIcon": "Icona", - "GameListHeaderApplication": "Nome", - "GameListHeaderDeveloper": "Sviluppatore", - "GameListHeaderVersion": "Versione", - "GameListHeaderTimePlayed": "Tempo di gioco", - "GameListHeaderLastPlayed": "Giocato l'ultima volta", - "GameListHeaderFileExtension": "Estensione", - "GameListHeaderFileSize": "Dimensione file", - "GameListHeaderPath": "Percorso", - "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", - "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", - "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", - "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", - "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", - "GameListContextMenuManageDlc": "Gestici DLC", - "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", - "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", - "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", - "GameListContextMenuCacheManagement": "Gestione della cache", - "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", - "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", - "GameListContextMenuExtractData": "Estrai dati", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", - "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", - "StatusBarSystemVersion": "Versione di sistema: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", - "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", - "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", - "LinuxVmMaxMapCountDialogButtonPersistent": "Sì, in modo permanente", - "LinuxVmMaxMapCountWarningTextPrimary": "La quantità massima di mappature di memoria è inferiore a quella consigliata.", - "LinuxVmMaxMapCountWarningTextSecondary": "Il valore corrente di vm.max_map_count ({0}) è inferiore a {1}. Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.\n\nPotresti voler aumentare manualmente il limite o installare pkexec, il che permette a Ryujinx di assisterlo.", - "Settings": "Impostazioni", - "SettingsTabGeneral": "Interfaccia utente", - "SettingsTabGeneralGeneral": "Generali", - "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", - "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", - "SettingsTabGeneralHideCursor": "Nascondi il cursore:", - "SettingsTabGeneralHideCursorNever": "Mai", - "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", - "SettingsTabGeneralHideCursorAlways": "Sempre", - "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", - "SettingsTabGeneralAdd": "Aggiungi", - "SettingsTabGeneralRemove": "Rimuovi", - "SettingsTabSystem": "Sistema", - "SettingsTabSystemCore": "Principale", - "SettingsTabSystemSystemRegion": "Regione del sistema:", - "SettingsTabSystemSystemRegionJapan": "Giappone", - "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", - "SettingsTabSystemSystemRegionEurope": "Europa", - "SettingsTabSystemSystemRegionAustralia": "Australia", - "SettingsTabSystemSystemRegionChina": "Cina", - "SettingsTabSystemSystemRegionKorea": "Corea", - "SettingsTabSystemSystemRegionTaiwan": "Taiwan", - "SettingsTabSystemSystemLanguage": "Lingua del sistema:", - "SettingsTabSystemSystemLanguageJapanese": "Giapponese", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", - "SettingsTabSystemSystemLanguageFrench": "Francese", - "SettingsTabSystemSystemLanguageGerman": "Tedesco", - "SettingsTabSystemSystemLanguageItalian": "Italiano", - "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", - "SettingsTabSystemSystemLanguageChinese": "Cinese", - "SettingsTabSystemSystemLanguageKorean": "Coreano", - "SettingsTabSystemSystemLanguageDutch": "Olandese", - "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", - "SettingsTabSystemSystemLanguageRussian": "Russo", - "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", - "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", - "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", - "SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:", - "SettingsTabSystemSystemTime": "Data e ora del sistema:", - "SettingsTabSystemEnableVsync": "Attiva VSync", - "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", - "SettingsTabSystemAudioBackend": "Backend audio:", - "SettingsTabSystemAudioBackendDummy": "Manichino", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "AudioIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Trucchi", - "SettingsTabSystemHacksNote": " (Possono causare instabilità)", - "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", - "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", - "SettingsTabGraphics": "Grafica", - "SettingsTabGraphicsAPI": "API Grafiche", - "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", - "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", - "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", - "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", - "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", - "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", - "SettingsTabLogging": "Log", - "SettingsTabLoggingLogging": "Log", - "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", - "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", - "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", - "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", - "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", - "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", - "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", - "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:", - "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", - "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", - "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", - "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", - "SettingsTabInput": "Input", - "SettingsTabInputEnableDockedMode": "Attiva modalità TV", - "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", - "SettingsButtonSave": "Salva", - "SettingsButtonClose": "Chiudi", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Cancella", - "SettingsButtonApply": "Applica", - "ControllerSettingsPlayer": "Giocatore", - "ControllerSettingsPlayer1": "Giocatore 1", - "ControllerSettingsPlayer2": "Giocatore 2", - "ControllerSettingsPlayer3": "Giocatore 3", - "ControllerSettingsPlayer4": "Giocatore 4", - "ControllerSettingsPlayer5": "Giocatore 5", - "ControllerSettingsPlayer6": "Giocatore 6", - "ControllerSettingsPlayer7": "Giocatore 7", - "ControllerSettingsPlayer8": "Giocatore 8", - "ControllerSettingsHandheld": "Portatile", - "ControllerSettingsInputDevice": "Dispositivo di input", - "ControllerSettingsRefresh": "Ricarica", - "ControllerSettingsDeviceDisabled": "Disabilitato", - "ControllerSettingsControllerType": "Tipo di controller", - "ControllerSettingsControllerTypeHandheld": "Portatile", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", - "ControllerSettingsProfile": "Profilo", - "ControllerSettingsProfileDefault": "Predefinito", - "ControllerSettingsLoad": "Carica", - "ControllerSettingsAdd": "Aggiungi", - "ControllerSettingsRemove": "Rimuovi", - "ControllerSettingsButtons": "Pulsanti", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Croce direzionale", - "ControllerSettingsDPadUp": "Su", - "ControllerSettingsDPadDown": "Giù", - "ControllerSettingsDPadLeft": "Sinistra", - "ControllerSettingsDPadRight": "Destra", - "ControllerSettingsStickButton": "Pulsante", - "ControllerSettingsStickUp": "Su", - "ControllerSettingsStickDown": "Giù", - "ControllerSettingsStickLeft": "Sinistra", - "ControllerSettingsStickRight": "Destra", - "ControllerSettingsStickStick": "Levetta", - "ControllerSettingsStickInvertXAxis": "Inverti levetta X", - "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", - "ControllerSettingsStickDeadzone": "Zona morta:", - "ControllerSettingsLStick": "Stick sinistro", - "ControllerSettingsRStick": "Stick destro", - "ControllerSettingsTriggersLeft": "Grilletto sinistro", - "ControllerSettingsTriggersRight": "Grilletto destro", - "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", - "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", - "ControllerSettingsTriggers": "Grilletti", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", - "ControllerSettingsExtraButtonsRight": "Tasto destro", - "ControllerSettingsMisc": "Varie", - "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", - "ControllerSettingsMotion": "Movimento", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", - "ControllerSettingsMotionControllerSlot": "Slot del controller:", - "ControllerSettingsMotionMirrorInput": "Input specchiato", - "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", - "ControllerSettingsMotionServerHost": "Server:", - "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", - "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", - "ControllerSettingsSave": "Salva", - "ControllerSettingsClose": "Chiudi", - "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", - "UserProfilesSaveProfileName": "Salva nome del profilo", - "UserProfilesChangeProfileImage": "Cambia immagine profilo", - "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", - "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", - "UserProfilesDelete": "Elimina", - "UserProfilesClose": "Chiudi", - "ProfileNameSelectionWatermark": "Scegli un soprannome", - "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", - "ProfileImageSelectionHeader": "Scegli un'immagine profilo", - "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", - "ProfileImageSelectionImportImage": "Importa file immagine", - "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", - "InputDialogTitle": "Finestra di dialogo ", - "InputDialogOk": "OK", - "InputDialogCancel": "Annulla", - "InputDialogAddNewProfileTitle": "Scegli il nome profilo", - "InputDialogAddNewProfileHeader": "Digita un nome profilo", - "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", - "AvatarChoose": "Scegli", - "AvatarSetBackgroundColor": "Imposta colore di sfondo", - "AvatarClose": "Chiudi", - "ControllerSettingsLoadProfileToolTip": "Carica profilo", - "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", - "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", - "ControllerSettingsSaveProfileToolTip": "Salva profilo", - "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", - "MenuBarFileToolsHideUi": "Nascondi UI", - "GameListContextMenuRunApplication": "Esegui applicazione", - "GameListContextMenuToggleFavorite": "Preferito", - "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", - "SettingsTabGeneralThemeBaseStyle": "Modalità", - "SettingsTabGeneralThemeBaseStyleDark": "Scura", - "SettingsTabGeneralThemeBaseStyleLight": "Chiara", - "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", - "ButtonBrowse": "Sfoglia", - "ControllerSettingsConfigureGeneral": "Configura", - "ControllerSettingsRumble": "Vibrazione", - "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", - "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", - "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?", - "DialogConfirmationTitle": "Ryujinx - Conferma", - "DialogUpdaterTitle": "Ryujinx - Aggiornamento", - "DialogErrorTitle": "Ryujinx - Errore", - "DialogWarningTitle": "Ryujinx - Avviso", - "DialogExitTitle": "Ryujinx - Esci", - "DialogErrorMessage": "Ryujinx ha riscontrato un problema", - "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", - "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", - "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", - "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", - "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", - "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", - "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", - "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", - "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", - "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", - "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", - "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", - "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", - "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", - "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", - "DialogUpdaterCompleteMessage": "Aggiornamento completato!", - "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", - "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", - "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", - "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", - "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", - "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", - "DialogRestartRequiredMessage": "Riavvio richiesto", - "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", - "DialogThemeRestartSubMessage": "Vuoi riavviare?", - "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", - "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", - "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", - "DialogInstallFileTypesSuccessMessage": "Tipi di file installati con successo!", - "DialogInstallFileTypesErrorMessage": "Impossibile installare i tipi di file.", - "DialogUninstallFileTypesSuccessMessage": "Tipi di file disinstallati con successo!", - "DialogUninstallFileTypesErrorMessage": "Disinstallazione dei tipi di file non riuscita.", - "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", - "DialogControllerAppletTitle": "Applet del controller", - "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", - "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", - "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", - "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", - "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", - "DialogProfileDeleteProfileTitle": "Eliminazione profilo", - "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", - "DialogWarning": "Avviso", - "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", - "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", - "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", - "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", - "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.", - "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", - "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", - "DialogUserProfileUnsavedChangesTitle": "Attenzione - Modifiche Non Salvate", - "DialogUserProfileUnsavedChangesMessage": "Hai apportato modifiche a questo profilo utente che non sono state salvate.", - "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", - "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", - "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", - "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", - "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", - "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", - "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", - "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Funzionalità", - "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", - "CommonAuto": "Auto", - "CommonOff": "Spento", - "CommonOn": "Acceso", - "InputDialogYes": "Si", - "InputDialogNo": "No", - "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", - "MenuBarOptionsPauseEmulation": "Pausa", - "MenuBarOptionsResumeEmulation": "Riprendi", - "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", - "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", - "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", - "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", - "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", - "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", - "AboutRyujinxAboutTitle": "Informazioni:", - "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", - "AboutRyujinxMaintainersTitle": "Mantenuto da:", - "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", - "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", - "AmiiboSeriesLabel": "Serie Amiibo", - "AmiiboCharacterLabel": "Personaggio", - "AmiiboScanButtonLabel": "Scannerizza", - "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", - "DlcManagerTableHeadingEnabledLabel": "Abilitato", - "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", - "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", - "DlcManagerTableHeadingFullPathLabel": "Percorso completo", - "DlcManagerRemoveAllButton": "Rimuovi tutti", - "DlcManagerEnableAllButton": "Abilita tutto", - "DlcManagerDisableAllButton": "Disabilita tutto", - "MenuBarOptionsChangeLanguage": "Cambia lingua", - "MenuBarShowFileTypes": "Mostra tipi di file", - "CommonSort": "Ordina", - "CommonShowNames": "Mostra nomi", - "CommonFavorite": "Preferito", - "OrderAscending": "Crescente", - "OrderDescending": "Decrescente", - "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", - "ErrorWindowTitle": "Finestra errore", - "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", - "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", - "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", - "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", - "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", - "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", - "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", - "DockModeToggleTooltip": "Attiva o disabilta modalità TV", - "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", - "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", - "RegionTooltip": "Cambia regione di sistema", - "LanguageTooltip": "Cambia lingua di sistema", - "TimezoneTooltip": "Cambia fuso orario di sistema", - "TimeTooltip": "Cambia data e ora di sistema", - "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", - "PptcToggleTooltip": "Attiva o disattiva PPTC", - "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", - "AudioBackendTooltip": "Cambia backend audio", - "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", - "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", - "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", - "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", - "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", - "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", - "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", - "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", - "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", - "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", - "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", - "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", - "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", - "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", - "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", - "FileLogTooltip": "Attiva o disattiva il logging su file", - "StubLogTooltip": "Attiva messaggi stub log", - "InfoLogTooltip": "Attiva messaggi info log", - "WarnLogTooltip": "Attiva messaggi warning log", - "ErrorLogTooltip": "Attiva messaggi error log", - "TraceLogTooltip": "Attiva messaggi trace log", - "GuestLogTooltip": "Attiva messaggi guest log", - "FileAccessLogTooltip": "Attiva messaggi file access log", - "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3", - "DeveloperOptionTooltip": "Usa con attenzione", - "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", - "DebugLogTooltip": "Attiva messaggi debug log", - "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", - "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", - "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", - "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", - "ExitTooltip": "Esci da Ryujinx", - "OpenSettingsTooltip": "Apri finestra delle impostazioni", - "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", - "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", - "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", - "OpenAboutTooltip": "Apri finestra delle informazioni", - "GridSize": "Dimensione griglia", - "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", - "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", - "SettingsTabSystemAudioVolume": "Volume: ", - "AudioVolumeTooltip": "Cambia volume audio", - "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", - "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", - "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", - "GameListContextMenuManageCheat": "Gestisci Cheats", - "ControllerSettingsStickRange": "Raggio:", - "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", - "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Audio", - "SettingsTabNetwork": "Rete", - "SettingsTabNetworkConnection": "Connessione di rete", - "SettingsTabCpuCache": "Cache CPU", - "SettingsTabCpuMemory": "Memoria CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", - "UpdaterDisabledWarningTitle": "Updater disabilitato!", - "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", - "ControllerSettingsRotate90": "Ruota in senso orario di 90°", - "IconSize": "Dimensioni icona", - "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", - "MenuBarOptionsShowConsole": "Mostra console", - "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", - "UserErrorNoKeys": "Chiavi non trovate", - "UserErrorNoFirmware": "Firmware non trovato", - "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", - "UserErrorApplicationNotFound": "Applicazione non trovata", - "UserErrorUnknown": "Errore sconosciuto", - "UserErrorUndefined": "Errore non definito", - "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", - "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", - "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", - "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", - "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", - "OpenSetupGuideMessage": "Apri la guida all'installazione", - "NoUpdate": "Nessun aggiornamento", - "TitleUpdateVersionLabel": "Versione {0} - {1}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Conferma", - "FileDialogAllTypes": "Tutti i tipi", - "Never": "Mai", - "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", - "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", - "SoftwareKeyboard": "Tastiera software", - "SoftwareKeyboardModeNumbersOnly": "Deve essere solo numeri", - "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", - "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", - "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", - "UpdaterRenaming": "Rinominazione dei vecchi files...", - "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", - "UpdaterAddingFiles": "Aggiunta nuovi files...", - "UpdaterExtracting": "Estrazione aggiornamento...", - "UpdaterDownloading": "Download aggiornamento...", - "Game": "Gioco", - "Docked": "TV", - "Handheld": "Portatile", - "ConnectionError": "Errore di connessione.", - "AboutPageDeveloperListMore": "{0} e altri ancora...", - "ApiError": "Errore dell'API.", - "LoadingHeading": "Caricamento di {0}", - "CompilingPPTC": "Compilazione PTC", - "CompilingShaders": "Compilazione Shaders", - "AllKeyboards": "Tutte le tastiere", - "OpenFileDialogTitle": "Seleziona un file supportato da aprire", - "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", - "AllSupportedFormats": "Tutti i formati supportati", - "RyujinxUpdater": "Aggiornamento Ryujinx", - "SettingsTabHotkeys": "Tasti di scelta rapida", - "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", - "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", - "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", - "SettingsTabHotkeysToggleMuteHotkey": "Muta:", - "ControllerMotionTitle": "Impostazioni dei sensori di movimento", - "ControllerRumbleTitle": "Impostazioni di vibrazione", - "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", - "SettingsXamlThemeFile": "File del tema xaml", - "AvatarWindowTitle": "Gestisci account - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Sconosciuto", - "Usage": "Utilizzo", - "Writable": "Scrivibile", - "SelectDlcDialogTitle": "Seleziona file dei DLC", - "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", - "UserProfileWindowTitle": "Gestisci profili degli utenti", - "CheatWindowTitle": "Gestisci cheat dei giochi", - "DlcWindowTitle": "Gestisci DLC dei giochi", - "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", - "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", - "BuildId": "ID Build", - "DlcWindowHeading": "DLC disponibili per {0} [{1}]", - "UserProfilesEditProfile": "Modifica selezionati", - "Cancel": "Annulla", - "Save": "Salva", - "Discard": "Scarta", - "UserProfilesSetProfileImage": "Imposta immagine profilo", - "UserProfileEmptyNameError": "È richiesto un nome", - "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", - "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", - "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", - "UserProfilesName": "Nome:", - "UserProfilesUserId": "ID utente:", - "SettingsTabGraphicsBackend": "Backend grafica", - "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", - "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", - "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", - "SettingsTabGraphicsPreferredGpu": "GPU preferita", - "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", - "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", - "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", - "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", - "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", - "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", - "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", - "SettingsEnableMacroHLE": "Abilita Macro HLE", - "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", - "SettingsEnableColorSpacePassthrough": "Spazio colore passante", - "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio colore. Per gli utenti con schermi ad ampia gamma, questo può risultare in colori più vivaci, al costo della correttezza del colore.", - "VolumeShort": "Vol", - "UserProfilesManageSaves": "Gestisci i salvataggi", - "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", - "IrreversibleActionNote": "Questa azione non è reversibile.", - "SaveManagerHeading": "Gestisci i salvataggi per {0}", - "SaveManagerTitle": "Gestione Salvataggi", - "Name": "Nome", - "Size": "Dimensione", - "Search": "Cerca", - "UserProfilesRecoverLostAccounts": "Recupera il tuo account", - "Recover": "Recupera", - "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", - "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", - "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco", - "GraphicsAALabel": "Anti-Aliasing:", - "GraphicsScalingFilterLabel": "Filtro di scala:", - "GraphicsScalingFilterTooltip": "Abilita scalatura Framebuffer", - "GraphicsScalingFilterLevelLabel": "Livello", - "GraphicsScalingFilterLevelTooltip": "Imposta livello del filtro di scala", - "SmaaLow": "SMAA Basso", - "SmaaMedium": "SMAA Medio", - "SmaaHigh": "SMAA Alto", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Modificare L'Utente", - "UserEditorTitleCreate": "Crea Un Utente", - "SettingsTabNetworkInterface": "Interfaccia di rete:", - "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN", - "NetworkInterfaceDefault": "Predefinito", - "PackagingShaders": "Comprimendo shader", - "AboutChangelogButton": "Visualizza changelog su GitHub", - "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ja_JP.json b/src/Ryujinx.Ava/Assets/Locales/ja_JP.json deleted file mode 100644 index 5b31c5f2..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/ja_JP.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "英語 (アメリカ)", - "MenuBarFileOpenApplet": "アプレットを開く", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", - "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", - "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", - "SettingsTabSystemMemoryManagerModeSoftware": "ソフトウェア", - "SettingsTabSystemMemoryManagerModeHost": "ホスト (高速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "ホスト, チェックなし (最高速, 安全でない)", - "SettingsTabSystemUseHypervisor": "ハイパーバイザーを使用", - "MenuBarFile": "ファイル(_F)", - "MenuBarFileOpenFromFile": "ファイルからアプリケーションをロード(_L)", - "MenuBarFileOpenUnpacked": "展開されたゲームをロード", - "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", - "MenuBarFileOpenLogsFolder": "ログフォルダを開く", - "MenuBarFileExit": "終了(_E)", - "MenuBarOptions": "オプション", - "MenuBarOptionsToggleFullscreen": "全画面切り替え", - "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", - "MenuBarOptionsStopEmulation": "エミュレーションを停止", - "MenuBarOptionsSettings": "設定(_S)", - "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", - "MenuBarActions": "アクション(_A)", - "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", - "MenuBarActionsScanAmiibo": "Amiibo をスキャン", - "MenuBarTools": "ツール(_T)", - "MenuBarToolsInstallFirmware": "ファームウェアをインストール", - "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", - "MenuBarFileToolsInstallFirmwareFromDirectory": "ディレクトリからファームウェアをインストール", - "MenuBarToolsManageFileTypes": "ファイル形式を管理", - "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", - "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", - "MenuBarHelp": "ヘルプ", - "MenuBarHelpCheckForUpdates": "アップデートを確認", - "MenuBarHelpAbout": "Ryujinx について", - "MenuSearch": "検索...", - "GameListHeaderFavorite": "お気に入り", - "GameListHeaderIcon": "アイコン", - "GameListHeaderApplication": "名称", - "GameListHeaderDeveloper": "開発元", - "GameListHeaderVersion": "バージョン", - "GameListHeaderTimePlayed": "プレイ時間", - "GameListHeaderLastPlayed": "最終プレイ日時", - "GameListHeaderFileExtension": "ファイル拡張子", - "GameListHeaderFileSize": "ファイルサイズ", - "GameListHeaderPath": "パス", - "GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます", - "GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます", - "GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます", - "GameListContextMenuManageTitleUpdates": "アップデートを管理", - "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", - "GameListContextMenuManageDlc": "DLCを管理", - "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", - "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", - "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", - "GameListContextMenuCacheManagement": "キャッシュ管理", - "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", - "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", - "GameListContextMenuCacheManagementPurgeShaderCache": "シェーダーキャッシュを破棄", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "アプリケーションのシェーダーキャッシュを破棄します", - "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC ディレクトリを開く", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "シェーダーキャッシュディレクトリを開く", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "アプリケーションのシェーダーキャッシュを格納するディレクトリを開きます", - "GameListContextMenuExtractData": "データを展開", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", - "GameListContextMenuExtractDataLogo": "ロゴ", - "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", - "StatusBarGamesLoaded": "{0}/{1} ゲーム", - "StatusBarSystemVersion": "システムバージョン: {0}", - "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", - "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count の値を {0}に増やしますか?", - "LinuxVmMaxMapCountDialogTextSecondary": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "はい, 次回再起動まで", - "LinuxVmMaxMapCountDialogButtonPersistent": "はい, 恒久的に", - "LinuxVmMaxMapCountWarningTextPrimary": "メモリマッピングの最大量が推奨値よりも小さいです.", - "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count の現在値 {0} は {1} よりも小さいです. ゲームによっては現在許可されている値よりも大きなメモリマッピングを作成しようとする場合があります. 上限を越えた場合, Ryujinx はクラッシュします.", - "Settings": "設定", - "SettingsTabGeneral": "ユーザインタフェース", - "SettingsTabGeneralGeneral": "一般", - "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする", - "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する", - "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する", - "SettingsTabGeneralHideCursor": "マウスカーソルを非表示", - "SettingsTabGeneralHideCursorNever": "決して", - "SettingsTabGeneralHideCursorOnIdle": "アイドル時", - "SettingsTabGeneralHideCursorAlways": "常時", - "SettingsTabGeneralGameDirectories": "ゲームディレクトリ", - "SettingsTabGeneralAdd": "追加", - "SettingsTabGeneralRemove": "削除", - "SettingsTabSystem": "システム", - "SettingsTabSystemCore": "コア", - "SettingsTabSystemSystemRegion": "地域:", - "SettingsTabSystemSystemRegionJapan": "日本", - "SettingsTabSystemSystemRegionUSA": "アメリカ", - "SettingsTabSystemSystemRegionEurope": "ヨーロッパ", - "SettingsTabSystemSystemRegionAustralia": "オーストラリア", - "SettingsTabSystemSystemRegionChina": "中国", - "SettingsTabSystemSystemRegionKorea": "韓国", - "SettingsTabSystemSystemRegionTaiwan": "台湾", - "SettingsTabSystemSystemLanguage": "言語:", - "SettingsTabSystemSystemLanguageJapanese": "日本語", - "SettingsTabSystemSystemLanguageAmericanEnglish": "英語(アメリカ)", - "SettingsTabSystemSystemLanguageFrench": "フランス語", - "SettingsTabSystemSystemLanguageGerman": "ドイツ語", - "SettingsTabSystemSystemLanguageItalian": "イタリア語", - "SettingsTabSystemSystemLanguageSpanish": "スペイン語", - "SettingsTabSystemSystemLanguageChinese": "中国語", - "SettingsTabSystemSystemLanguageKorean": "韓国語", - "SettingsTabSystemSystemLanguageDutch": "オランダ語", - "SettingsTabSystemSystemLanguagePortuguese": "ポルトガル語", - "SettingsTabSystemSystemLanguageRussian": "ロシア語", - "SettingsTabSystemSystemLanguageTaiwanese": "台湾語", - "SettingsTabSystemSystemLanguageBritishEnglish": "英語(イギリス)", - "SettingsTabSystemSystemLanguageCanadianFrench": "フランス語(カナダ)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "スペイン語(ラテンアメリカ)", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "中国語", - "SettingsTabSystemSystemLanguageTraditionalChinese": "台湾語", - "SettingsTabSystemSystemTimeZone": "タイムゾーン:", - "SettingsTabSystemSystemTime": "時刻:", - "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック", - "SettingsTabSystemAudioBackend": "音声バックエンド:", - "SettingsTabSystemAudioBackendDummy": "ダミー", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "ハック", - "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", - "SettingsTabSystemExpandDramSize": "DRAMサイズを6GiBに拡大する", - "SettingsTabSystemIgnoreMissingServices": "未実装サービスを無視する", - "SettingsTabGraphics": "グラフィックス", - "SettingsTabGraphicsAPI": "グラフィックスAPI", - "SettingsTabGraphicsEnableShaderCache": "シェーダーキャッシュを有効にする", - "SettingsTabGraphicsAnisotropicFiltering": "異方性フィルタリング:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "解像度:", - "SettingsTabGraphicsResolutionScaleCustom": "カスタム (非推奨)", - "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "アスペクト比:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "ウインドウサイズに合わせる", - "SettingsTabGraphicsDeveloperOptions": "開発者向けオプション", - "SettingsTabGraphicsShaderDumpPath": "グラフィックス シェーダー ダンプパス:", - "SettingsTabLogging": "ロギング", - "SettingsTabLoggingLogging": "ロギング", - "SettingsTabLoggingEnableLoggingToFile": "ファイルへのロギングを有効にする", - "SettingsTabLoggingEnableStubLogs": "Stub ログを有効にする", - "SettingsTabLoggingEnableInfoLogs": "Info ログを有効にする", - "SettingsTabLoggingEnableWarningLogs": "Warning ログを有効にする", - "SettingsTabLoggingEnableErrorLogs": "Error ログを有効にする", - "SettingsTabLoggingEnableTraceLogs": "Trace ログを有効にする", - "SettingsTabLoggingEnableGuestLogs": "Guest ログを有効にする", - "SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効にする", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:", - "SettingsTabLoggingDeveloperOptions": "開発者オプション", - "SettingsTabLoggingDeveloperOptionsNote": "警告: パフォーマンスを低下させます", - "SettingsTabLoggingGraphicsBackendLogLevel": "グラフィックスバックエンド ログレベル:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "なし", - "SettingsTabLoggingGraphicsBackendLogLevelError": "エラー", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて", - "SettingsTabLoggingEnableDebugLogs": "デバッグログを有効にする", - "SettingsTabInput": "入力", - "SettingsTabInputEnableDockedMode": "ドッキングモード", - "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", - "SettingsButtonSave": "セーブ", - "SettingsButtonClose": "閉じる", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "キャンセル", - "SettingsButtonApply": "適用", - "ControllerSettingsPlayer": "プレイヤー", - "ControllerSettingsPlayer1": "プレイヤー 1", - "ControllerSettingsPlayer2": "プレイヤー 2", - "ControllerSettingsPlayer3": "プレイヤー 3", - "ControllerSettingsPlayer4": "プレイヤー 4", - "ControllerSettingsPlayer5": "プレイヤー 5", - "ControllerSettingsPlayer6": "プレイヤー 6", - "ControllerSettingsPlayer7": "プレイヤー 7", - "ControllerSettingsPlayer8": "プレイヤー 8", - "ControllerSettingsHandheld": "携帯", - "ControllerSettingsInputDevice": "入力デバイス", - "ControllerSettingsRefresh": "更新", - "ControllerSettingsDeviceDisabled": "無効", - "ControllerSettingsControllerType": "コントローラ種別", - "ControllerSettingsControllerTypeHandheld": "携帯", - "ControllerSettingsControllerTypeProController": "Pro コントローラ", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon ペア", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 左", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon 右", - "ControllerSettingsProfile": "プロファイル", - "ControllerSettingsProfileDefault": "デフォルト", - "ControllerSettingsLoad": "ロード", - "ControllerSettingsAdd": "追加", - "ControllerSettingsRemove": "削除", - "ControllerSettingsButtons": "ボタン", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "十字キー", - "ControllerSettingsDPadUp": "上", - "ControllerSettingsDPadDown": "下", - "ControllerSettingsDPadLeft": "左", - "ControllerSettingsDPadRight": "右", - "ControllerSettingsStickButton": "ボタン", - "ControllerSettingsStickUp": "上", - "ControllerSettingsStickDown": "下", - "ControllerSettingsStickLeft": "左", - "ControllerSettingsStickRight": "右", - "ControllerSettingsStickStick": "スティック", - "ControllerSettingsStickInvertXAxis": "X軸を反転", - "ControllerSettingsStickInvertYAxis": "Y軸を反転", - "ControllerSettingsStickDeadzone": "遊び:", - "ControllerSettingsLStick": "左スティック", - "ControllerSettingsRStick": "右スティック", - "ControllerSettingsTriggersLeft": "左トリガー", - "ControllerSettingsTriggersRight": "右トリガー", - "ControllerSettingsTriggersButtonsLeft": "左トリガーボタン", - "ControllerSettingsTriggersButtonsRight": "右トリガーボタン", - "ControllerSettingsTriggers": "トリガー", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "左ボタン", - "ControllerSettingsExtraButtonsRight": "右ボタン", - "ControllerSettingsMisc": "その他", - "ControllerSettingsTriggerThreshold": "トリガーしきい値:", - "ControllerSettingsMotion": "モーション", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 互換モーションを使用", - "ControllerSettingsMotionControllerSlot": "コントローラ スロット:", - "ControllerSettingsMotionMirrorInput": "入力反転", - "ControllerSettingsMotionRightJoyConSlot": "JoyCon 右 スロット:", - "ControllerSettingsMotionServerHost": "サーバ:", - "ControllerSettingsMotionGyroSensitivity": "ジャイロ感度:", - "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", - "ControllerSettingsSave": "セーブ", - "ControllerSettingsClose": "閉じる", - "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", - "UserProfilesSaveProfileName": "プロファイル名をセーブ", - "UserProfilesChangeProfileImage": "プロファイル画像を変更", - "UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:", - "UserProfilesAddNewProfile": "プロファイルを作成", - "UserProfilesDelete": "削除", - "UserProfilesClose": "閉じる", - "ProfileNameSelectionWatermark": "ニックネームを選択", - "ProfileImageSelectionTitle": "プロファイル画像選択", - "ProfileImageSelectionHeader": "プロファイル画像を選択", - "ProfileImageSelectionNote": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます", - "ProfileImageSelectionImportImage": "画像ファイルをインポート", - "ProfileImageSelectionSelectAvatar": "ファームウェア内のアバターを選択", - "InputDialogTitle": "入力ダイアログ", - "InputDialogOk": "OK", - "InputDialogCancel": "キャンセル", - "InputDialogAddNewProfileTitle": "プロファイル名を選択", - "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", - "InputDialogAddNewProfileSubtext": "(最大長: {0})", - "AvatarChoose": "選択", - "AvatarSetBackgroundColor": "背景色を指定", - "AvatarClose": "閉じる", - "ControllerSettingsLoadProfileToolTip": "プロファイルをロード", - "ControllerSettingsAddProfileToolTip": "プロファイルを追加", - "ControllerSettingsRemoveProfileToolTip": "プロファイルを削除", - "ControllerSettingsSaveProfileToolTip": "プロファイルをセーブ", - "MenuBarFileToolsTakeScreenshot": "スクリーンショットを撮影", - "MenuBarFileToolsHideUi": "UIを隠す", - "GameListContextMenuRunApplication": "アプリケーションを実行", - "GameListContextMenuToggleFavorite": "お気に入りを切り替え", - "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", - "SettingsTabGeneralTheme": "テーマ", - "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", - "SettingsTabGeneralThemeBaseStyle": "基本スタイル", - "SettingsTabGeneralThemeBaseStyleDark": "ダーク", - "SettingsTabGeneralThemeBaseStyleLight": "ライト", - "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効にする", - "ButtonBrowse": "参照", - "ControllerSettingsConfigureGeneral": "設定", - "ControllerSettingsRumble": "振動", - "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", - "ControllerSettingsRumbleWeakMultiplier": "弱振動の補正値", - "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] のセーブデータはありません", - "DialogMessageSaveNotAvailableCreateSaveMessage": "このゲームのセーブデータを作成してよろしいですか?", - "DialogConfirmationTitle": "Ryujinx - 確認", - "DialogUpdaterTitle": "Ryujinx - アップデータ", - "DialogErrorTitle": "Ryujinx - エラー", - "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 終了", - "DialogErrorMessage": "エラーが発生しました", - "DialogExitMessage": "Ryujinx を閉じてよろしいですか?", - "DialogExitSubMessage": "セーブされていないデータはすべて失われます!", - "DialogMessageCreateSaveErrorMessage": "セーブデータ: {0} の作成中にエラーが発生しました", - "DialogMessageFindSaveErrorMessage": "セーブデータ: {0} の検索中にエラーが発生しました", - "FolderDialogExtractTitle": "展開フォルダを選択", - "DialogNcaExtractionMessage": "{1} から {0} セクションを展開中...", - "DialogNcaExtractionTitle": "Ryujinx - NCA セクション展開", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "展開に失敗しました. 選択されたファイルにはメイン NCA が存在しません.", - "DialogNcaExtractionCheckLogErrorMessage": "展開に失敗しました. 詳細はログを確認してください.", - "DialogNcaExtractionSuccessMessage": "展開が正常終了しました", - "DialogUpdaterConvertFailedMessage": "現在の Ryujinx バージョンの変換に失敗しました.", - "DialogUpdaterCancelUpdateMessage": "アップデータをキャンセル中!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "最新バージョンの Ryujinx を使用中です!", - "DialogUpdaterFailedToGetVersionMessage": "Github からのリリース情報取得時にエラーが発生しました. Github Actions でリリースファイルを作成中かもしれません. 後ほどもう一度試してみてください.", - "DialogUpdaterConvertFailedGithubMessage": "Github から取得した Ryujinx バージョンの変換に失敗しました.", - "DialogUpdaterDownloadingMessage": "アップデートをダウンロード中...", - "DialogUpdaterExtractionMessage": "アップデートを展開中...", - "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", - "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", - "DialogUpdaterCompleteMessage": "アップデート完了!", - "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", - "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", - "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", - "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", - "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", - "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", - "DialogUpdaterDirtyBuildSubMessage": "サポートされているバージョンをお探しなら, https://ryujinx.org/ で Ryujinx をダウンロードしてください.", - "DialogRestartRequiredMessage": "再起動が必要", - "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", - "DialogThemeRestartSubMessage": "再起動しますか", - "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", - "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", - "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", - "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", - "DialogInstallFileTypesErrorMessage": "ファイル形式のインストールに失敗しました.", - "DialogUninstallFileTypesSuccessMessage": "ファイル形式のアンインストールに成功しました!", - "DialogUninstallFileTypesErrorMessage": "ファイル形式のアンインストールに失敗しました.", - "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", - "DialogControllerAppletTitle": "コントローラアプレット", - "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", - "DialogErrorAppletErrorExceptionMessage": "エラーアプレットダイアログ表示エラー: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nこのエラーへの対処方法については, セットアップガイドを参照してください.", - "DialogUserErrorDialogTitle": "Ryujinx エラー ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "API からの情報取得中にエラーが発生しました.", - "DialogAmiiboApiConnectErrorMessage": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.", - "DialogProfileInvalidProfileErrorMessage": "プロファイル {0} は現在の入力設定システムと互換性がありません.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "デフォルトのプロファイルは上書きできません", - "DialogProfileDeleteProfileTitle": "プロファイルを削除中", - "DialogProfileDeleteProfileMessage": "このアクションは元に戻せません. 本当に続けてよろしいですか?", - "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?", - "DialogPPTCDeletionErrorMessage": "PPTC キャッシュ破棄エラー {0}: {1}", - "DialogShaderDeletionMessage": "シェーダーキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?", - "DialogShaderDeletionErrorMessage": "シェーダーキャッシュ破棄エラー {0}: {1}", - "DialogRyujinxErrorMessage": "エラーが発生しました", - "DialogInvalidTitleIdErrorMessage": "UI エラー: 選択されたゲームは有効なタイトル ID を保持していません", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} には有効なシステムファームウェアがありません.", - "DialogFirmwareInstallerFirmwareInstallTitle": "ファームウェア {0} をインストール", - "DialogFirmwareInstallerFirmwareInstallMessage": "システムバージョン {0} がインストールされます.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n現在のシステムバージョン {0} を置き換えます.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n続けてよろしいですか?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "ファームウェアをインストール中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "システムバージョン {0} が正常にインストールされました.", - "DialogUserProfileDeletionWarningMessage": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります", - "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", - "DialogUserProfileUnsavedChangesTitle": "警告 - 保存されていない変更", - "DialogUserProfileUnsavedChangesMessage": "保存されていないユーザプロファイルを変更しました.", - "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", - "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", - "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", - "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", - "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", - "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", - "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", - "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", - "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", - "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", - "SettingsTabGraphicsFeaturesOptions": "機能", - "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", - "CommonAuto": "自動", - "CommonOff": "オフ", - "CommonOn": "オン", - "InputDialogYes": "はい", - "InputDialogNo": "いいえ", - "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", - "MenuBarOptionsPauseEmulation": "中断", - "MenuBarOptionsResumeEmulation": "再開", - "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", - "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", - "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", - "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", - "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", - "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", - "AboutRyujinxAboutTitle": "Ryujinx について:", - "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", - "AboutRyujinxMaintainersTitle": "開発者:", - "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", - "AboutRyujinxSupprtersTitle": "Patreon での支援者:", - "AmiiboSeriesLabel": "Amiibo シリーズ", - "AmiiboCharacterLabel": "キャラクタ", - "AmiiboScanButtonLabel": "スキャン", - "AmiiboOptionsShowAllLabel": "すべての Amiibo を表示", - "AmiiboOptionsUsRandomTagLabel": "ハック: ランダムな Uuid を使用", - "DlcManagerTableHeadingEnabledLabel": "有効", - "DlcManagerTableHeadingTitleIdLabel": "タイトルID", - "DlcManagerTableHeadingContainerPathLabel": "コンテナパス", - "DlcManagerTableHeadingFullPathLabel": "フルパス", - "DlcManagerRemoveAllButton": "すべて削除", - "DlcManagerEnableAllButton": "すべて有効", - "DlcManagerDisableAllButton": "すべて無効", - "MenuBarOptionsChangeLanguage": "言語を変更", - "MenuBarShowFileTypes": "ファイル形式を表示", - "CommonSort": "並べ替え", - "CommonShowNames": "名称を表示", - "CommonFavorite": "お気に入り", - "OrderAscending": "昇順", - "OrderDescending": "降順", - "SettingsTabGraphicsFeatures": "機能", - "ErrorWindowTitle": "エラーウインドウ", - "ToggleDiscordTooltip": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します", - "AddGameDirBoxTooltip": "リストに追加するゲームディレクトリを入力します", - "AddGameDirTooltip": "リストにゲームディレクトリを追加します", - "RemoveGameDirTooltip": "選択したゲームディレクトリを削除します", - "CustomThemeCheckTooltip": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します", - "CustomThemePathTooltip": "カスタム GUI テーマのパスです", - "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", - "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", - "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", - "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", - "RegionTooltip": "システムの地域を変更します", - "LanguageTooltip": "システムの言語を変更します", - "TimezoneTooltip": "システムのタイムゾーンを変更します", - "TimeTooltip": "システムの時刻を変更します", - "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", - "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", - "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", - "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", - "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", - "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", - "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", - "MemoryManagerUnsafeTooltip": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.", - "UseHypervisorTooltip": "JIT の代わりにハイパーバイザーを使用します. 利用可能な場合, パフォーマンスが大幅に向上しますが, 現在の状態では不安定になる可能性があります.", - "DRamTooltip": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.", - "IgnoreMissingServicesTooltip": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.", - "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", - "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", - "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", - "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", - "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", - "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", - "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", - "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", - "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", - "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "InfoLogTooltip": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "WarnLogTooltip": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "ErrorLogTooltip": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "TraceLogTooltip": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "GuestLogTooltip": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", - "FileAccessLogTooltip": "ファイルアクセスログメッセージをコンソールに出力します.", - "FSAccessLogModeTooltip": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です", - "DeveloperOptionTooltip": "使用上の注意", - "OpenGlLogLevel": "適切なログレベルを有効にする必要があります", - "DebugLogTooltip": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", - "LoadApplicationFileTooltip": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます", - "LoadApplicationFolderTooltip": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます", - "OpenRyujinxFolderTooltip": "Ryujinx ファイルシステムフォルダを開きます", - "OpenRyujinxLogsTooltip": "ログが格納されるフォルダを開きます", - "ExitTooltip": "Ryujinx を終了します", - "OpenSettingsTooltip": "設定ウインドウを開きます", - "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", - "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", - "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", - "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", - "GridSize": "グリッドサイズ", - "GridSizeTooltip": "グリッドサイズを変更します", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "ポルトガル語(ブラジル)", - "AboutRyujinxContributorsButtonHeader": "すべての貢献者を確認", - "SettingsTabSystemAudioVolume": "音量: ", - "AudioVolumeTooltip": "音量を変更します", - "SettingsTabSystemEnableInternetAccess": "ゲストインターネットアクセス / LAN モード", - "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", - "GameListContextMenuManageCheatToolTip": "チートを管理します", - "GameListContextMenuManageCheat": "チートを管理", - "ControllerSettingsStickRange": "範囲:", - "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", - "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "音声", - "SettingsTabNetwork": "ネットワーク", - "SettingsTabNetworkConnection": "ネットワーク接続", - "SettingsTabCpuCache": "CPU キャッシュ", - "SettingsTabCpuMemory": "CPU メモリ", - "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", - "UpdaterDisabledWarningTitle": "アップデータは無効です!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", - "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", - "ControllerSettingsRotate90": "時計回りに 90° 回転", - "IconSize": "アイコンサイズ", - "IconSizeTooltip": "ゲームアイコンのサイズを変更します", - "MenuBarOptionsShowConsole": "コンソールを表示", - "ShaderCachePurgeError": "シェーダーキャッシュの破棄エラー {0}: {1}", - "UserErrorNoKeys": "Keys がありません", - "UserErrorNoFirmware": "ファームウェアがありません", - "UserErrorFirmwareParsingFailed": "ファームウェアのパーズエラー", - "UserErrorApplicationNotFound": "アプリケーションがありません", - "UserErrorUnknown": "不明なエラー", - "UserErrorUndefined": "未定義エラー", - "UserErrorNoKeysDescription": "'prod.keys' が見つかりませんでした", - "UserErrorNoFirmwareDescription": "インストールされたファームウェアが見つかりませんでした", - "UserErrorFirmwareParsingFailedDescription": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.", - "UserErrorApplicationNotFoundDescription": "指定されたパスに有効なアプリケーションがありませんでした.", - "UserErrorUnknownDescription": "不明なエラーが発生しました!", - "UserErrorUndefinedDescription": "未定義のエラーが発生しました! 発生すべきものではないので,開発者にご連絡ください!", - "OpenSetupGuideMessage": "セットアップガイドを開く", - "NoUpdate": "アップデートなし", - "TitleUpdateVersionLabel": "バージョン {0} - {1}", - "RyujinxInfo": "Ryujinx - 情報", - "RyujinxConfirm": "Ryujinx - 確認", - "FileDialogAllTypes": "すべての種別", - "Never": "決して", - "SwkbdMinCharacters": "最低 {0} 文字必要です", - "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", - "SoftwareKeyboard": "ソフトウェアキーボード", - "SoftwareKeyboardModeNumbersOnly": "数字のみ", - "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", - "SoftwareKeyboardModeASCII": "ASCII文字列のみ", - "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", - "UpdaterRenaming": "古いファイルをリネーム中...", - "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", - "UpdaterAddingFiles": "新規ファイルを追加中...", - "UpdaterExtracting": "アップデートを展開中...", - "UpdaterDownloading": "アップデートをダウンロード中...", - "Game": "ゲーム", - "Docked": "ドッキング", - "Handheld": "携帯", - "ConnectionError": "接続エラー.", - "AboutPageDeveloperListMore": "{0}, その他大勢...", - "ApiError": "API エラー.", - "LoadingHeading": "ロード中: {0}", - "CompilingPPTC": "PTC をコンパイル中", - "CompilingShaders": "シェーダーをコンパイル中", - "AllKeyboards": "すべてのキーボード", - "OpenFileDialogTitle": "開くファイルを選択", - "OpenFolderDialogTitle": "展開されたゲームフォルダを選択", - "AllSupportedFormats": "すべての対応フォーマット", - "RyujinxUpdater": "Ryujinx アップデータ", - "SettingsTabHotkeys": "キーボード ホットキー", - "SettingsTabHotkeysHotkeys": "キーボード ホットキー", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", - "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", - "SettingsTabHotkeysShowUiHotkey": "UI表示:", - "SettingsTabHotkeysPauseHotkey": "中断:", - "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", - "ControllerMotionTitle": "モーションコントロール設定", - "ControllerRumbleTitle": "振動設定", - "SettingsSelectThemeFileDialogTitle": "テーマファイルを選択", - "SettingsXamlThemeFile": "Xaml テーマファイル", - "AvatarWindowTitle": "アカウント - アバター管理", - "Amiibo": "Amiibo", - "Unknown": "不明", - "Usage": "使用法", - "Writable": "書き込み可能", - "SelectDlcDialogTitle": "DLC ファイルを選択", - "SelectUpdateDialogTitle": "アップデートファイルを選択", - "UserProfileWindowTitle": "ユーザプロファイルを管理", - "CheatWindowTitle": "チート管理", - "DlcWindowTitle": "DLC 管理", - "UpdateWindowTitle": "アップデート管理", - "CheatWindowHeading": "利用可能なチート {0} [{1}]", - "BuildId": "ビルドID:", - "DlcWindowHeading": "利用可能な DLC {0} [{1}]", - "UserProfilesEditProfile": "編集", - "Cancel": "キャンセル", - "Save": "セーブ", - "Discard": "破棄", - "UserProfilesSetProfileImage": "プロファイル画像を設定", - "UserProfileEmptyNameError": "名称が必要です", - "UserProfileNoImageError": "プロファイル画像が必要です", - "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", - "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", - "UserProfilesName": "名称:", - "UserProfilesUserId": "ユーザID:", - "SettingsTabGraphicsBackend": "グラフィックスバックエンド", - "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", - "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", - "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", - "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", - "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", - "SettingsGpuBackendRestartMessage": "グラフィックスバックエンドまたはGPUの設定が変更されました. 変更を適用するには再起動する必要があります", - "SettingsGpuBackendRestartSubMessage": "今すぐ再起動しますか?", - "RyujinxUpdaterMessage": "Ryujinx を最新版にアップデートしますか?", - "SettingsTabHotkeysVolumeUpHotkey": "音量を上げる:", - "SettingsTabHotkeysVolumeDownHotkey": "音量を下げる:", - "SettingsEnableMacroHLE": "マクロの高レベルエミュレーション (HLE) を有効にする", - "SettingsEnableMacroHLETooltip": "GPU マクロコードの高レベルエミュレーションです.\n\nパフォーマンスを向上させますが, 一部のゲームでグラフィックに不具合が発生する可能性があります.\n\nよくわからない場合はオンのままにしてください.", - "SettingsEnableColorSpacePassthrough": "色空間をパススルー", - "SettingsEnableColorSpacePassthroughTooltip": "Vulkan バックエンドに対して, 色空間を指定せずに色情報を渡します. 高色域ディスプレイを使用する場合, 正確ではないですがより鮮やかな色になる可能性があります.", - "VolumeShort": "音量", - "UserProfilesManageSaves": "セーブデータの管理", - "DeleteUserSave": "このゲームのユーザセーブデータを削除しますか?", - "IrreversibleActionNote": "この操作は元に戻せません.", - "SaveManagerHeading": "{0} のセーブデータを管理", - "SaveManagerTitle": "セーブデータマネージャ", - "Name": "名称", - "Size": "サイズ", - "Search": "検索", - "UserProfilesRecoverLostAccounts": "アカウントの復旧", - "Recover": "復旧", - "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", - "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", - "GraphicsAATooltip": "ゲームのレンダリングにアンチエイリアスを適用します", - "GraphicsAALabel": "アンチエイリアス:", - "GraphicsScalingFilterLabel": "スケーリングフィルタ:", - "GraphicsScalingFilterTooltip": "フレームバッファスケーリングを有効にします", - "GraphicsScalingFilterLevelLabel": "レベル", - "GraphicsScalingFilterLevelTooltip": "スケーリングフィルタのレベルを設定", - "SmaaLow": "SMAA Low", - "SmaaMedium": "SMAA Medium", - "SmaaHigh": "SMAA High", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "ユーザを編集", - "UserEditorTitleCreate": "ユーザを作成", - "SettingsTabNetworkInterface": "ネットワークインタフェース:", - "NetworkInterfaceTooltip": "LAN機能に使用されるネットワークインタフェース", - "NetworkInterfaceDefault": "デフォルト", - "PackagingShaders": "シェーダーを構築中", - "AboutChangelogButton": "GitHub で更新履歴を表示", - "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ko_KR.json b/src/Ryujinx.Ava/Assets/Locales/ko_KR.json deleted file mode 100644 index cdc61722..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/ko_KR.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "한국어", - "MenuBarFileOpenApplet": "애플릿 열기", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", - "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", - "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", - "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", - "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트 확인 안함 (가장 빠르나 안전하지 않음)", - "SettingsTabSystemUseHypervisor": "하이퍼바이저 사용하기", - "MenuBarFile": "_파일", - "MenuBarFileOpenFromFile": "_파일에서 응용 프로그램 불러오기", - "MenuBarFileOpenUnpacked": "_압축을 푼 게임 불러오기", - "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", - "MenuBarFileOpenLogsFolder": "로그 폴더 열기", - "MenuBarFileExit": "_종료", - "MenuBarOptions": "옵션", - "MenuBarOptionsToggleFullscreen": "전체화면 전환", - "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", - "MenuBarOptionsStopEmulation": "에뮬레이션 중지", - "MenuBarOptionsSettings": "_설정", - "MenuBarOptionsManageUserProfiles": "_사용자 프로파일 관리", - "MenuBarActions": "_동작", - "MenuBarOptionsSimulateWakeUpMessage": "깨우기 메시지 시뮬레이션", - "MenuBarActionsScanAmiibo": "Amiibo 스캔", - "MenuBarTools": "_도구", - "MenuBarToolsInstallFirmware": "펌웨어 설치", - "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", - "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉터리에서 펌웨어 설치", - "MenuBarToolsManageFileTypes": "파일 형식 관리", - "MenuBarToolsInstallFileTypes": "파일 형식 설치", - "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", - "MenuBarHelp": "도움말", - "MenuBarHelpCheckForUpdates": "업데이트 확인", - "MenuBarHelpAbout": "정보", - "MenuSearch": "검색...", - "GameListHeaderFavorite": "즐겨찾기", - "GameListHeaderIcon": "아이콘", - "GameListHeaderApplication": "이름", - "GameListHeaderDeveloper": "개발자", - "GameListHeaderVersion": "버전", - "GameListHeaderTimePlayed": "플레이 시간", - "GameListHeaderLastPlayed": "마지막 플레이", - "GameListHeaderFileExtension": "파일 확장자", - "GameListHeaderFileSize": "파일 크기", - "GameListHeaderPath": "경로", - "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", - "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉터리 열기", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", - "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", - "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리", - "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", - "GameListContextMenuManageDlc": "DLC 관리", - "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", - "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", - "GameListContextMenuOpenModsDirectoryToolTip": "응용프로그램의 Mod가 포함된 디렉터리 열기", - "GameListContextMenuCacheManagement": "캐시 관리", - "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", - "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", - "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "응용프로그램 셰이더 캐시 삭제\n", - "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉터리 열기", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용프로그램 PPTC 캐시가 포함된 디렉터리 열기", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉터리 열기", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용프로그램 셰이더 캐시가 포함된 디렉터리 열기", - "GameListContextMenuExtractData": "데이터 추출", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "응용프로그램의 현재 구성에서 ExeFS 추출 (업데이트 포함)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", - "GameListContextMenuExtractDataLogo": "로고", - "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", - "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", - "StatusBarSystemVersion": "시스템 버전 : {0}", - "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", - "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count의 값을 {0}으로 늘리시겠습니까?", - "LinuxVmMaxMapCountDialogTextSecondary": "일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "예, 다음에 다시 시작할 때까지", - "LinuxVmMaxMapCountDialogButtonPersistent": "예, 영구적으로", - "LinuxVmMaxMapCountWarningTextPrimary": "메모리 매핑의 최대 용량이 권장 용량보다 적습니다.", - "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count({0})의 현재 값이 {1}보다 낮습니다. 일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.\n\n수동으로 제한을 늘리거나 Ryujinx의 도움을 받을 수 있는 pkexec을 설치하는 것이 좋습니다.", - "Settings": "설정", - "SettingsTabGeneral": "사용자 인터페이스", - "SettingsTabGeneralGeneral": "일반", - "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화", - "SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인", - "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", - "SettingsTabGeneralHideCursor": "마우스 커서 숨기기", - "SettingsTabGeneralHideCursorNever": "절대 안 함", - "SettingsTabGeneralHideCursorOnIdle": "유휴 상태", - "SettingsTabGeneralHideCursorAlways": "언제나", - "SettingsTabGeneralGameDirectories": "게임 디렉터리", - "SettingsTabGeneralAdd": "추가", - "SettingsTabGeneralRemove": "제거", - "SettingsTabSystem": "시스템", - "SettingsTabSystemCore": "코어", - "SettingsTabSystemSystemRegion": "시스템 지역:", - "SettingsTabSystemSystemRegionJapan": "일본", - "SettingsTabSystemSystemRegionUSA": "미국", - "SettingsTabSystemSystemRegionEurope": "유럽", - "SettingsTabSystemSystemRegionAustralia": "호주", - "SettingsTabSystemSystemRegionChina": "중국", - "SettingsTabSystemSystemRegionKorea": "한국", - "SettingsTabSystemSystemRegionTaiwan": "대만", - "SettingsTabSystemSystemLanguage": "시스템 언어 :", - "SettingsTabSystemSystemLanguageJapanese": "일본어", - "SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)", - "SettingsTabSystemSystemLanguageFrench": "프랑스어", - "SettingsTabSystemSystemLanguageGerman": "독일어", - "SettingsTabSystemSystemLanguageItalian": "이탈리아어", - "SettingsTabSystemSystemLanguageSpanish": "스페인어", - "SettingsTabSystemSystemLanguageChinese": "중국어", - "SettingsTabSystemSystemLanguageKorean": "한국어", - "SettingsTabSystemSystemLanguageDutch": "네덜란드어", - "SettingsTabSystemSystemLanguagePortuguese": "포르투갈어", - "SettingsTabSystemSystemLanguageRussian": "러시아어", - "SettingsTabSystemSystemLanguageTaiwanese": "대만어", - "SettingsTabSystemSystemLanguageBritishEnglish": "영어(영국)", - "SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어(캐나다)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어(라틴 아메리카)", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체", - "SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체", - "SettingsTabSystemSystemTimeZone": "시스템 시간대:", - "SettingsTabSystemSystemTime": "시스템 시간:", - "SettingsTabSystemEnableVsync": "수직 동기화", - "SettingsTabSystemEnablePptc": "PPTC(프로파일된 영구 번역 캐시)", - "SettingsTabSystemEnableFsIntegrityChecks": "파일 시스템 무결성 검사", - "SettingsTabSystemAudioBackend": "음향 후단부 :", - "SettingsTabSystemAudioBackendDummy": "더미", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "사운드IO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "해킹", - "SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음", - "SettingsTabSystemExpandDramSize": "대체 메모리 레이아웃 사용(개발자)", - "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", - "SettingsTabGraphics": "그래픽", - "SettingsTabGraphicsAPI": "그래픽 API", - "SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화", - "SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링 :", - "SettingsTabGraphicsAnisotropicFilteringAuto": "자동", - "SettingsTabGraphicsAnisotropicFiltering2x": "2배", - "SettingsTabGraphicsAnisotropicFiltering4x": "4배", - "SettingsTabGraphicsAnisotropicFiltering8x": "8배", - "SettingsTabGraphicsAnisotropicFiltering16x": "16배", - "SettingsTabGraphicsResolutionScale": "해상도 배율 :", - "SettingsTabGraphicsResolutionScaleCustom": "사용자 정의(권장하지 않음)", - "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4배(2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "종횡비 :", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기", - "SettingsTabGraphicsDeveloperOptions": "개발자 옵션", - "SettingsTabGraphicsShaderDumpPath": "그래픽 셰이더 덤프 경로 :", - "SettingsTabLogging": "로그 기록", - "SettingsTabLoggingLogging": "로그 기록", - "SettingsTabLoggingEnableLoggingToFile": "파일에 로그 기록 활성화", - "SettingsTabLoggingEnableStubLogs": "스텁 로그 활성화", - "SettingsTabLoggingEnableInfoLogs": "정보 로그 활성화", - "SettingsTabLoggingEnableWarningLogs": "경고 로그 활성화", - "SettingsTabLoggingEnableErrorLogs": "오류 로그 활성화", - "SettingsTabLoggingEnableTraceLogs": "추적 로그 활성화", - "SettingsTabLoggingEnableGuestLogs": "게스트 로그 활성화", - "SettingsTabLoggingEnableFsAccessLogs": "Fs 접속 로그 활성화", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 접속 로그 모드 :", - "SettingsTabLoggingDeveloperOptions": "개발자 옵션", - "SettingsTabLoggingDeveloperOptionsNote": "경고: 성능이 저하됨", - "SettingsTabLoggingGraphicsBackendLogLevel": "그래픽 후단부 로그 수준 :", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "없음", - "SettingsTabLoggingGraphicsBackendLogLevelError": "오류", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "느려짐", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "모두", - "SettingsTabLoggingEnableDebugLogs": "디버그 로그 활성화", - "SettingsTabInput": "입력", - "SettingsTabInputEnableDockedMode": "도킹 모드", - "SettingsTabInputDirectKeyboardAccess": "직접 키보드 접속", - "SettingsButtonSave": "저장", - "SettingsButtonClose": "닫기", - "SettingsButtonOk": "확인", - "SettingsButtonCancel": "취소", - "SettingsButtonApply": "적용", - "ControllerSettingsPlayer": "플레이어", - "ControllerSettingsPlayer1": "플레이어 1", - "ControllerSettingsPlayer2": "플레이어 2", - "ControllerSettingsPlayer3": "플레이어 3", - "ControllerSettingsPlayer4": "플레이어 4", - "ControllerSettingsPlayer5": "플레이어 5", - "ControllerSettingsPlayer6": "플레이어 6", - "ControllerSettingsPlayer7": "플레이어 7", - "ControllerSettingsPlayer8": "플레이어 8", - "ControllerSettingsHandheld": "휴대 모드", - "ControllerSettingsInputDevice": "입력 장치", - "ControllerSettingsRefresh": "새로 고침", - "ControllerSettingsDeviceDisabled": "비활성화됨", - "ControllerSettingsControllerType": "컨트롤러 유형", - "ControllerSettingsControllerTypeHandheld": "휴대 모드", - "ControllerSettingsControllerTypeProController": "프로 컨트롤러", - "ControllerSettingsControllerTypeJoyConPair": "조이콘 페어링", - "ControllerSettingsControllerTypeJoyConLeft": "좌측 조이콘", - "ControllerSettingsControllerTypeJoyConRight": "우측 조이콘", - "ControllerSettingsProfile": "프로필", - "ControllerSettingsProfileDefault": "기본", - "ControllerSettingsLoad": "불러오기", - "ControllerSettingsAdd": "추가", - "ControllerSettingsRemove": "제거", - "ControllerSettingsButtons": "버튼", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "방향 패드", - "ControllerSettingsDPadUp": "↑", - "ControllerSettingsDPadDown": "↓", - "ControllerSettingsDPadLeft": "←", - "ControllerSettingsDPadRight": "→", - "ControllerSettingsStickButton": "버튼", - "ControllerSettingsStickUp": "↑", - "ControllerSettingsStickDown": "↓", - "ControllerSettingsStickLeft": "←", - "ControllerSettingsStickRight": "→", - "ControllerSettingsStickStick": "스틱", - "ControllerSettingsStickInvertXAxis": "스틱 X 축 반전", - "ControllerSettingsStickInvertYAxis": "스틱 Y 축 반전", - "ControllerSettingsStickDeadzone": "사각지대 :", - "ControllerSettingsLStick": "좌측 스틱", - "ControllerSettingsRStick": "우측 스틱", - "ControllerSettingsTriggersLeft": "좌측 트리거", - "ControllerSettingsTriggersRight": "우측 트리거", - "ControllerSettingsTriggersButtonsLeft": "좌측 트리거 버튼", - "ControllerSettingsTriggersButtonsRight": "우측 트리거 버튼", - "ControllerSettingsTriggers": "트리거 버튼", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "좌측 버튼", - "ControllerSettingsExtraButtonsRight": "우측 버튼", - "ControllerSettingsMisc": "기타", - "ControllerSettingsTriggerThreshold": "트리거 임계값 :", - "ControllerSettingsMotion": "동작", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", - "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :", - "ControllerSettingsMotionMirrorInput": "미러 입력", - "ControllerSettingsMotionRightJoyConSlot": "우측 조이콘 슬롯 :", - "ControllerSettingsMotionServerHost": "서버 호스트 :", - "ControllerSettingsMotionGyroSensitivity": "자이로 감도 :", - "ControllerSettingsMotionGyroDeadzone": "자이로 사각지대 :", - "ControllerSettingsSave": "저장", - "ControllerSettingsClose": "닫기", - "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", - "UserProfilesSaveProfileName": "프로필 이름 저장", - "UserProfilesChangeProfileImage": "프로필 이미지 변경", - "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :", - "UserProfilesAddNewProfile": "프로필 생성", - "UserProfilesDelete": "삭제", - "UserProfilesClose": "닫기", - "ProfileNameSelectionWatermark": "닉네임을 입력하세요", - "ProfileImageSelectionTitle": "프로필 이미지 선택", - "ProfileImageSelectionHeader": "프로필 이미지 선택", - "ProfileImageSelectionNote": "사용자 지정 프로필 이미지를 가져오거나 시스템 펌웨어에서 아바타 선택 가능", - "ProfileImageSelectionImportImage": "이미지 파일 가져오기", - "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", - "InputDialogTitle": "입력 대화상자", - "InputDialogOk": "확인", - "InputDialogCancel": "취소", - "InputDialogAddNewProfileTitle": "프로필 이름 선택", - "InputDialogAddNewProfileHeader": "프로필 이름 입력", - "InputDialogAddNewProfileSubtext": "(최대 길이 : {0})", - "AvatarChoose": "선택", - "AvatarSetBackgroundColor": "배경색 설정", - "AvatarClose": "닫기", - "ControllerSettingsLoadProfileToolTip": "프로필 불러오기", - "ControllerSettingsAddProfileToolTip": "프로필 추가", - "ControllerSettingsRemoveProfileToolTip": "프로필 제거", - "ControllerSettingsSaveProfileToolTip": "프로필 저장", - "MenuBarFileToolsTakeScreenshot": "스크린 샷 찍기", - "MenuBarFileToolsHideUi": "UI 숨기기", - "GameListContextMenuRunApplication": "응용프로그램 실행", - "GameListContextMenuToggleFavorite": "즐겨찾기 전환", - "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", - "SettingsTabGeneralTheme": "테마", - "SettingsTabGeneralThemeCustomTheme": "커스텀 테마 경로", - "SettingsTabGeneralThemeBaseStyle": "기본 스타일", - "SettingsTabGeneralThemeBaseStyleDark": "어두움", - "SettingsTabGeneralThemeBaseStyleLight": "밝음", - "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", - "ButtonBrowse": "찾아보기", - "ControllerSettingsConfigureGeneral": "구성", - "ControllerSettingsRumble": "진동", - "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", - "ControllerSettingsRumbleWeakMultiplier": "약한 진동 증폭기", - "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}]에 대한 저장 데이터가 없음", - "DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임에 대한 저장 데이터를 생성하겠습니까?", - "DialogConfirmationTitle": "Ryujinx - 확인", - "DialogUpdaterTitle": "Ryujinx - 업데이터", - "DialogErrorTitle": "Ryujinx - 오류", - "DialogWarningTitle": "Ryujinx - 경고", - "DialogExitTitle": "Ryujinx - 종료", - "DialogErrorMessage": "Ryujinx 오류 발생", - "DialogExitMessage": "Ryujinx를 종료하겠습니까?", - "DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!", - "DialogMessageCreateSaveErrorMessage": "지정된 저장 데이터를 작성하는 중에 오류 발생: {0}", - "DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류 발생: {0}", - "FolderDialogExtractTitle": "추출할 폴더 선택", - "DialogNcaExtractionMessage": "{1}에서 {0} 섹션을 추출하는 중...", - "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패하였습니다. 선택한 파일에 기본 NCA가 없습니다.", - "DialogNcaExtractionCheckLogErrorMessage": "추출 실패하였습니다. 자세한 내용은 로그 파일을 읽으세요.", - "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", - "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", - "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중 입니다!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", - "DialogUpdaterFailedToGetVersionMessage": "GitHub 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하세요.", - "DialogUpdaterConvertFailedGithubMessage": "Github 개정에서 받은 Ryujinx 버전을 변환하지 못했습니다.", - "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중...", - "DialogUpdaterExtractionMessage": "업데이트 추출 중...", - "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸는 중...", - "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", - "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", - "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", - "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", - "DialogUpdaterArchNotSupportedSubMessage": "(64비트 시스템만 지원됩니다!)", - "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", - "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", - "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", - "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하세요.", - "DialogRestartRequiredMessage": "재시작 필요", - "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", - "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", - "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", - "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", - "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", - "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", - "DialogInstallFileTypesErrorMessage": "파일 형식을 설치하지 못했습니다.", - "DialogUninstallFileTypesSuccessMessage": "파일 형식을 성공적으로 제거했습니다!", - "DialogUninstallFileTypesErrorMessage": "파일 형식을 제거하지 못했습니다.", - "DialogOpenSettingsWindowLabel": "설정 창 열기", - "DialogControllerAppletTitle": "컨트롤러 애플릿", - "DialogMessageDialogErrorExceptionMessage": "메시지 대화상자를 표시하는 동안 오류 발생 : {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류 발생 : {0}", - "DialogErrorAppletErrorExceptionMessage": "오류에플릿 대화상자를 표시하는 동안 오류 발생 : {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르세요.", - "DialogUserErrorDialogTitle": "Ryuijnx 오류 ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", - "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인해야 할 수 있습니다.", - "DialogProfileInvalidProfileErrorMessage": "{0} 프로필은 현재 입력 구성 시스템과 호환되지 않습니다.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없음", - "DialogProfileDeleteProfileTitle": "프로필 삭제", - "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하겠습니까?", - "DialogWarning": "경고", - "DialogPPTCDeletionMessage": "다음 부팅 시, PPTC 재구축을 대기열에 추가 :\n\n{0}\n\n계속하겠습니까?", - "DialogPPTCDeletionErrorMessage": "{0}에서 PPTC 캐시 삭제 오류 : {1}", - "DialogShaderDeletionMessage": "다음에 대한 셰이더 캐시 삭제 :\n\n{0}\n\n계속하겠습니까?", - "DialogShaderDeletionErrorMessage": "{0}에서 셰이더 캐시 제거 오류 : {1}", - "DialogRyujinxErrorMessage": "Ryujinx에 오류 발생", - "DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 타이틀 ID가 없음", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", - "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", - "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}이(가) 설치됩니다.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 시스템 버전 {0}을(를) 대체합니다.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하겠습니까?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치 중...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}이(가) 성공적으로 설치되었습니다.", - "DialogUserProfileDeletionWarningMessage": "선택한 프로파일이 삭제되면 사용 가능한 다른 프로파일이 없음", - "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", - "DialogUserProfileUnsavedChangesTitle": "경고 - 변경사항 저장되지 않음", - "DialogUserProfileUnsavedChangesMessage": "저장되지 않은 사용자 프로파일을 수정했습니다.", - "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", - "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", - "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", - "DialogLoadNcaErrorMessage": "{0}. 오류 발생 파일 : {1}", - "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", - "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "개발자만 사용하도록 설계된 셰이더 덤프를 활성화했습니다.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 세이더 덤핑을 비활성화하는 것이 좋습니다. 지금 세이더 덤핑을 비활성화하겠습니까?", - "DialogLoadAppGameAlreadyLoadedMessage": "이미 게임 불러옴", - "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으세요.", - "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", - "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", - "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", - "SettingsTabGraphicsFeaturesOptions": "기능", - "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", - "CommonAuto": "자동", - "CommonOff": "끔", - "CommonOn": "켬", - "InputDialogYes": "예", - "InputDialogNo": "아니오", - "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 문자가 포함되어 있습니다. 다시 시도하세요.", - "MenuBarOptionsPauseEmulation": "일시 정지", - "MenuBarOptionsResumeEmulation": "다시 시작", - "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하세요.", - "AboutDisclaimerMessage": "Ryujinx는 닌텐도™,\n또는 그 파트너와 제휴한 바가 없습니다.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", - "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", - "AboutGithubUrlTooltipMessage": "기본 브라우저에서 Ryujinx GitHub 페이지를 열려면 클릭하세요.", - "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx 디스코드 서버에 대한 초대를 열려면 클릭하세요.", - "AboutTwitterUrlTooltipMessage": "기본 브라우저에서 Ryujinx 트위터 페이지를 열려면 클릭하세요.", - "AboutRyujinxAboutTitle": "정보 :", - "AboutRyujinxAboutContent": "Ryujinx는 닌텐도 스위치™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n트위터나 디스코드에서 최신 소식을 받아보세요.\n기여에 참여하고자 하는 개발자는 GitHub 또는 디스코드에서 자세한 내용을 확인할 수 있습니다.", - "AboutRyujinxMaintainersTitle": "유지 관리 :", - "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하세요.", - "AboutRyujinxSupprtersTitle": "Patreon에서 후원:", - "AmiiboSeriesLabel": "Amiibo 시리즈", - "AmiiboCharacterLabel": "캐릭터", - "AmiiboScanButtonLabel": "스캔", - "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", - "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", - "DlcManagerTableHeadingEnabledLabel": "활성화됨", - "DlcManagerTableHeadingTitleIdLabel": "타이틀 ID", - "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", - "DlcManagerTableHeadingFullPathLabel": "전체 경로", - "DlcManagerRemoveAllButton": "모두 제거", - "DlcManagerEnableAllButton": "모두 활성화", - "DlcManagerDisableAllButton": "모두 비활성화", - "MenuBarOptionsChangeLanguage": "언어 변경", - "MenuBarShowFileTypes": "파일 유형 표시", - "CommonSort": "정렬", - "CommonShowNames": "이름 표시", - "CommonFavorite": "즐겨찾기", - "OrderAscending": "오름차순", - "OrderDescending": "내림차순", - "SettingsTabGraphicsFeatures": "기능ㆍ개선 사항", - "ErrorWindowTitle": "오류 창", - "ToggleDiscordTooltip": "\"현재 재생 중인\" 디스코드 활동에 Ryujinx를 표시할지 여부 선택", - "AddGameDirBoxTooltip": "목록에 추가할 게임 디렉터리 입력", - "AddGameDirTooltip": "목록에 게임 디렉터리 추가", - "RemoveGameDirTooltip": "선택한 게임 디렉터리 제거", - "CustomThemeCheckTooltip": "GUI에 사용자 지정 Avalonia 테마를 사용하여 에뮬레이터 메뉴의 모양 변경", - "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", - "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", - "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", - "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", - "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", - "RegionTooltip": "시스템 지역 변경", - "LanguageTooltip": "시스템 언어 변경", - "TimezoneTooltip": "시스템 시간대 변경", - "TimeTooltip": "시스템 시간 변경", - "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", - "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", - "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", - "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", - "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.", - "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", - "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", - "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 접속하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 접속할 수 있으므로 이 모드에서는 신뢰할 수 있는 프로그램만 실행하세요.", - "UseHypervisorTooltip": "JIT 대신 하이퍼바이저를 사용합니다. 하이퍼바이저를 사용할 수 있을 때 성능을 향상시키지만, 현재 상태에서는 불안정할 수 있습니다.", - "DRamTooltip": "대체 메모리모드 레이아웃을 활용하여 스위치 개발 모델을 모방합니다.\n\n고해상도 텍스처 팩 또는 4k 해상도 모드에만 유용합니다. 성능을 향상시키지 않습니다.\n\n확실하지 않으면 꺼 두세요.", - "IgnoreMissingServicesTooltip": "구현되지 않은 호라이즌 OS 서비스를 무시합니다. 이것은 특정 게임을 부팅할 때 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n확실하지 않으면 꺼 두세요.", - "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", - "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", - "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", - "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", - "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", - "AnisotropyTooltip": "비등방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", - "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", - "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", - "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", - "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "InfoLogTooltip": "콘솔에 정보 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "WarnLogTooltip": "콘솔에 경고 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "ErrorLogTooltip": "콘솔에 오류 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "TraceLogTooltip": "콘솔에 추적 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "GuestLogTooltip": "콘솔에 게스트 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", - "FileAccessLogTooltip": "콘솔에 파일 액세스 로그 메시지를 인쇄합니다.", - "FSAccessLogModeTooltip": "콘솔에 대한 FS 접속 로그 출력을 활성화합니다. 가능한 모드는 0-3\t\t\t\t", - "DeveloperOptionTooltip": "주의해서 사용", - "OpenGlLogLevel": "적절한 로그 수준을 활성화해야 함", - "DebugLogTooltip": "콘솔에 디버그 로그 메시지를 인쇄합니다.\n\n로그를 읽기 어렵게 만들고 에뮬레이터 성능을 악화시키므로 직원이 구체적으로 지시한 경우에만 사용하세요.", - "LoadApplicationFileTooltip": "파일 탐색기를 열어 불러올 스위치 호환 파일 선택", - "LoadApplicationFolderTooltip": "파일 탐색기를 열어 불러올 스위치 호환 압축 해제 응용 프로그램 선택", - "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", - "OpenRyujinxLogsTooltip": "로그가 기록된 폴더 열기", - "ExitTooltip": "Ryujinx 종료", - "OpenSettingsTooltip": "설정 창 열기", - "OpenProfileManagerTooltip": "사용자 프로파일 관리자 창 열기", - "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아감", - "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", - "OpenAboutTooltip": "정보 창 열기", - "GridSize": "격자 크기", - "GridSizeTooltip": "격자 항목의 크기 변경", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)", - "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", - "SettingsTabSystemAudioVolume": "음량 : ", - "AudioVolumeTooltip": "음향 음량 변경", - "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 접속/LAN 모드", - "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", - "GameListContextMenuManageCheatToolTip": "치트 관리", - "GameListContextMenuManageCheat": "치트 관리", - "ControllerSettingsStickRange": "범위 :", - "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", - "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "오디오", - "SettingsTabNetwork": "네트워크", - "SettingsTabNetworkConnection": "네트워크 연결", - "SettingsTabCpuCache": "CPU 캐시", - "SettingsTabCpuMemory": "CPU 모드", - "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", - "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", - "GameListContextMenuOpenSdModsDirectory": "분위기 모드 디렉터리 열기", - "GameListContextMenuOpenSdModsDirectoryToolTip": "응용프로그램의 모드가 포함된 대체 SD 카드 분위기 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.", - "ControllerSettingsRotate90": "시계 방향으로 90° 회전", - "IconSize": "아이콘 크기", - "IconSizeTooltip": "게임 아이콘 크기 변경", - "MenuBarOptionsShowConsole": "콘솔 표시", - "ShaderCachePurgeError": "{0}에서 셰이더 캐시를 제거하는 중 오류 발생: {1}", - "UserErrorNoKeys": "키를 찾을 수 없음", - "UserErrorNoFirmware": "펌웨어를 찾을 수 없음", - "UserErrorFirmwareParsingFailed": "펌웨어 구문 분석 오류", - "UserErrorApplicationNotFound": "응용 프로그램을 찾을 수 없음", - "UserErrorUnknown": "알 수 없는 오류", - "UserErrorUndefined": "정의되지 않은 오류", - "UserErrorNoKeysDescription": "Ryujinx가 'prod.keys' 파일을 찾을 수 없음", - "UserErrorNoFirmwareDescription": "Ryujinx가 설치된 펌웨어를 찾을 수 없음", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx가 제공된 펌웨어를 구문 분석할 수 없습니다. 일반적으로 오래된 키가 원인입니다.", - "UserErrorApplicationNotFoundDescription": "Ryujinx가 지정된 경로에서 유효한 응용 프로그램을 찾을 수 없습니다.", - "UserErrorUnknownDescription": "알 수 없는 오류가 발생했습니다!", - "UserErrorUndefinedDescription": "정의되지 않은 오류가 발생했습니다! 이런 일이 발생하면 안 되므로, 개발자에게 문의하세요!", - "OpenSetupGuideMessage": "설정 가이드 열기", - "NoUpdate": "업데이트 없음", - "TitleUpdateVersionLabel": "버전 {0}", - "RyujinxInfo": "Ryujinx - 정보", - "RyujinxConfirm": "Ryujinx - 확인", - "FileDialogAllTypes": "모든 유형", - "Never": "절대 안 함", - "SwkbdMinCharacters": "{0}자 이상이어야 함", - "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", - "SoftwareKeyboard": "소프트웨어 키보드", - "SoftwareKeyboardModeNumbersOnly": "숫자만 가능", - "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", - "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", - "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하는 {0} 명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletMessage": "응용 프로그램은 다음을 사용하는 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletDockModeSet": "독 모드가 설정되었습니다. 휴대 모드도 유효하지 않습니다.", - "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", - "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", - "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", - "UpdaterExtracting": "업데이트를 추출하는 중...", - "UpdaterDownloading": "업데이트 다운로드 중...", - "Game": "게임", - "Docked": "도킹됨", - "Handheld": "휴대용", - "ConnectionError": "연결 오류입니다.", - "AboutPageDeveloperListMore": "{0} 등...", - "ApiError": "API 오류입니다.", - "LoadingHeading": "{0} 로딩 중", - "CompilingPPTC": "PTC 컴파일 중", - "CompilingShaders": "셰이더 컴파일 중", - "AllKeyboards": "모든 키보드", - "OpenFileDialogTitle": "지원되는 파일을 선택", - "OpenFolderDialogTitle": "압축을 푼 게임이 있는 폴더 선택", - "AllSupportedFormats": "지원되는 모든 형식", - "RyujinxUpdater": "Ryujinx 업데이터", - "SettingsTabHotkeys": "키보드 단축키", - "SettingsTabHotkeysHotkeys": "키보드 단축키", - "SettingsTabHotkeysToggleVsyncHotkey": "수직 동기화 전환 :", - "SettingsTabHotkeysScreenshotHotkey": "스크린샷 :", - "SettingsTabHotkeysShowUiHotkey": "UI 표시 :", - "SettingsTabHotkeysPauseHotkey": "일시 중지 :", - "SettingsTabHotkeysToggleMuteHotkey": "음 소거 :", - "ControllerMotionTitle": "동작 제어 설정", - "ControllerRumbleTitle": "진동 설정", - "SettingsSelectThemeFileDialogTitle": "테마 파일 선택", - "SettingsXamlThemeFile": "Xaml 테마 파일", - "AvatarWindowTitle": "계정 관리 - 아바타", - "Amiibo": "Amiibo", - "Unknown": "알 수 없음", - "Usage": "사용법", - "Writable": "쓰기 가능", - "SelectDlcDialogTitle": "DLC 파일 선택", - "SelectUpdateDialogTitle": "업데이트 파일 선택", - "UserProfileWindowTitle": "사용자 프로파일 관리자", - "CheatWindowTitle": "치트 관리자", - "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", - "UpdateWindowTitle": "타이틀 업데이트 관리자", - "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", - "BuildId": "빌드ID :", - "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", - "UserProfilesEditProfile": "선택된 항목 편집", - "Cancel": "취소", - "Save": "저장", - "Discard": "삭제", - "UserProfilesSetProfileImage": "프로파일 이미지 설정", - "UserProfileEmptyNameError": "이름 필요", - "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", - "GameUpdateWindowHeading": "{0} ({1})에 대한 업데이트 관리", - "SettingsTabHotkeysResScaleUpHotkey": "해상도 증가 :", - "SettingsTabHotkeysResScaleDownHotkey": "해상도 감소 :", - "UserProfilesName": "이름 :", - "UserProfilesUserId": "사용자 ID :", - "SettingsTabGraphicsBackend": "그래픽 후단부", - "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 후단부", - "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", - "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", - "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", - "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", - "SettingsGpuBackendRestartMessage": "그래픽 후단부 또는 GPU 설정이 수정되었습니다. 적용하려면 다시 시작해야 합니다.", - "SettingsGpuBackendRestartSubMessage": "지금 다시 시작하겠습니까?", - "RyujinxUpdaterMessage": "Ryujinx를 최신 버전으로 업데이트하겠습니까?", - "SettingsTabHotkeysVolumeUpHotkey": "음량 증가 :", - "SettingsTabHotkeysVolumeDownHotkey": "음량 감소 :", - "SettingsEnableMacroHLE": "매크로 HLE 활성화", - "SettingsEnableMacroHLETooltip": "GPU 매크로 코드의 높은 수준 에뮬레이션입니다.\n\n성능이 향상되지만 일부 게임에서 그래픽 결함이 발생할 수 있습니다.\n\n확실하지 않으면 켜 두세요.", - "SettingsEnableColorSpacePassthrough": "색 공간 통과", - "SettingsEnableColorSpacePassthroughTooltip": "색 공간을 지정하지 않고 색상 정보를 전달하도록 Vulkan 후단에 지시합니다. 와이드 가멋 디스플레이를 사용하는 사용자의 경우 색 정확도가 저하되지만 더 생생한 색상을 얻을 수 있습니다.", - "VolumeShort": "음량", - "UserProfilesManageSaves": "저장 관리", - "DeleteUserSave": "이 게임에 대한 사용자 저장을 삭제하겠습니까?", - "IrreversibleActionNote": "이 작업은 되돌릴 수 없습니다.", - "SaveManagerHeading": "{0} ({1})의 저장 관리", - "SaveManagerTitle": "저장 관리자", - "Name": "이름", - "Size": "크기", - "Search": "검색", - "UserProfilesRecoverLostAccounts": "잃어버린 계정 복구", - "Recover": "복구", - "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", - "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", - "GraphicsAATooltip": "게임 렌더링에 안티 앨리어싱을 적용", - "GraphicsAALabel": "안티 앨리어싱:", - "GraphicsScalingFilterLabel": "스케일링 필터:", - "GraphicsScalingFilterTooltip": "프레임버퍼 스케일링 활성화", - "GraphicsScalingFilterLevelLabel": "수준", - "GraphicsScalingFilterLevelTooltip": "스케일링 필터 수준 설정", - "SmaaLow": "SMAA 낮음", - "SmaaMedium": "SMAA 중간", - "SmaaHigh": "SMAA 높음", - "SmaaUltra": "SMAA 울트라", - "UserEditorTitle": "사용자 수정", - "UserEditorTitleCreate": "사용자 생성", - "SettingsTabNetworkInterface": "네트워크 인터페이스:", - "NetworkInterfaceTooltip": "LAN 기능에 사용되는 네트워크 인터페이스", - "NetworkInterfaceDefault": "기본", - "PackagingShaders": "셰이더 패키징 중", - "AboutChangelogButton": "GitHub에서 변경 로그 보기", - "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/pl_PL.json b/src/Ryujinx.Ava/Assets/Locales/pl_PL.json deleted file mode 100644 index 7edf41e8..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/pl_PL.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Angielski (USA)", - "MenuBarFileOpenApplet": "Otwórz Aplet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", - "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", - "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", - "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", - "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", - "SettingsTabSystemUseHypervisor": "Użyj Hiperwizora", - "MenuBarFile": "_Plik", - "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", - "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", - "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", - "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", - "MenuBarFileExit": "_Wyjdź", - "MenuBarOptions": "Opcje", - "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", - "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", - "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", - "MenuBarOptionsSettings": "_Ustawienia", - "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", - "MenuBarActions": "_Akcje", - "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", - "MenuBarActionsScanAmiibo": "Skanuj Amiibo", - "MenuBarTools": "_Narzędzia", - "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", - "MenuBarToolsManageFileTypes": "Zarządzaj rodzajem plików", - "MenuBarToolsInstallFileTypes": "Instalacja typów plików", - "MenuBarToolsUninstallFileTypes": "Odinstaluj rodzaje plików", - "MenuBarHelp": "Pomoc", - "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", - "MenuBarHelpAbout": "O Aplikacji", - "MenuSearch": "Wyszukaj...", - "GameListHeaderFavorite": "Ulub", - "GameListHeaderIcon": "Ikona", - "GameListHeaderApplication": "Nazwa", - "GameListHeaderDeveloper": "Deweloper", - "GameListHeaderVersion": "Wersja", - "GameListHeaderTimePlayed": "Czas Gry", - "GameListHeaderLastPlayed": "Ostatnio Grane", - "GameListHeaderFileExtension": "Rozsz. Pliku", - "GameListHeaderFileSize": "Rozm. Pliku", - "GameListHeaderPath": "Ścieżka", - "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", - "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", - "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", - "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", - "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", - "GameListContextMenuManageDlc": "Zarządzaj DLC", - "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", - "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", - "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", - "GameListContextMenuCacheManagement": "Zarządzanie Cache", - "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", - "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", - "GameListContextMenuExtractData": "Wyodrębnij Dane", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", - "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", - "StatusBarSystemVersion": "Wersja Systemu: {0}", - "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla mapowania pamięci", - "LinuxVmMaxMapCountDialogTextPrimary": "Czy chciałbyś zwiększyć wartość vm.max_map_count do {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie, jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", - "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", - "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość mapowania pamięci jest mniejsza niż zalecana.", - "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", - "Settings": "Ustawienia", - "SettingsTabGeneral": "Interfejs Użytkownika", - "SettingsTabGeneralGeneral": "Ogólne", - "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", - "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", - "SettingsTabGeneralHideCursor": "Ukryj kursor:", - "SettingsTabGeneralHideCursorNever": "Nigdy", - "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", - "SettingsTabGeneralHideCursorAlways": "Zawsze", - "SettingsTabGeneralGameDirectories": "Katalogi Gier", - "SettingsTabGeneralAdd": "Dodaj", - "SettingsTabGeneralRemove": "Usuń", - "SettingsTabSystem": "System", - "SettingsTabSystemCore": "Główne", - "SettingsTabSystemSystemRegion": "Region Systemu:", - "SettingsTabSystemSystemRegionJapan": "Japonia", - "SettingsTabSystemSystemRegionUSA": "USA", - "SettingsTabSystemSystemRegionEurope": "Europa", - "SettingsTabSystemSystemRegionAustralia": "Australia", - "SettingsTabSystemSystemRegionChina": "Chiny", - "SettingsTabSystemSystemRegionKorea": "Korea", - "SettingsTabSystemSystemRegionTaiwan": "Tajwan", - "SettingsTabSystemSystemLanguage": "Język Systemu:", - "SettingsTabSystemSystemLanguageJapanese": "Japoński", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", - "SettingsTabSystemSystemLanguageFrench": "Francuski", - "SettingsTabSystemSystemLanguageGerman": "Niemiecki", - "SettingsTabSystemSystemLanguageItalian": "Włoski", - "SettingsTabSystemSystemLanguageSpanish": "Hiszpański", - "SettingsTabSystemSystemLanguageChinese": "Chiński", - "SettingsTabSystemSystemLanguageKorean": "Koreański", - "SettingsTabSystemSystemLanguageDutch": "Holenderski", - "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", - "SettingsTabSystemSystemLanguageRussian": "Rosyjski", - "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", - "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", - "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", - "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", - "SettingsTabSystemSystemTime": "Czas Systemu:", - "SettingsTabSystemEnableVsync": "Włącz synchronizację pionową", - "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", - "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", - "SettingsTabSystemAudioBackend": "Backend Dżwięku:", - "SettingsTabSystemAudioBackendDummy": "Atrapa", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacki", - "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", - "SettingsTabSystemExpandDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", - "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", - "SettingsTabGraphics": "Grafika", - "SettingsTabGraphicsAPI": "Graficzne API", - "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", - "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczny", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", - "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", - "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", - "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", - "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", - "SettingsTabLogging": "Logowanie", - "SettingsTabLoggingLogging": "Logowanie", - "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", - "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", - "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", - "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", - "SettingsTabLoggingEnableErrorLogs": "Włącz Logi Błędów", - "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", - "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", - "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", - "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", - "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", - "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Zredukuje wydajność", - "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", - "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", - "SettingsTabInput": "Sterowanie", - "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", - "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", - "SettingsButtonSave": "Zapisz", - "SettingsButtonClose": "Zamknij", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Anuluj", - "SettingsButtonApply": "Zastosuj", - "ControllerSettingsPlayer": "Gracz", - "ControllerSettingsPlayer1": "Gracz 1", - "ControllerSettingsPlayer2": "Gracz 2", - "ControllerSettingsPlayer3": "Gracz 3", - "ControllerSettingsPlayer4": "Gracz 4", - "ControllerSettingsPlayer5": "Gracz 5", - "ControllerSettingsPlayer6": "Gracz 6", - "ControllerSettingsPlayer7": "Gracz 7", - "ControllerSettingsPlayer8": "Gracz 8", - "ControllerSettingsHandheld": "Przenośny", - "ControllerSettingsInputDevice": "Urządzenie Wejściowe", - "ControllerSettingsRefresh": "Odśwież", - "ControllerSettingsDeviceDisabled": "Wyłączone", - "ControllerSettingsControllerType": "Typ Kontrolera", - "ControllerSettingsControllerTypeHandheld": "Przenośny", - "ControllerSettingsControllerTypeProController": "Pro Kontroler", - "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", - "ControllerSettingsControllerTypeJoyConLeft": "Lewy JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "Prawy JoyCon", - "ControllerSettingsProfile": "Profil", - "ControllerSettingsProfileDefault": "Domyślny", - "ControllerSettingsLoad": "Wczytaj", - "ControllerSettingsAdd": "Dodaj", - "ControllerSettingsRemove": "Usuń", - "ControllerSettingsButtons": "Przyciski", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Pad Kierunkowy", - "ControllerSettingsDPadUp": "Góra", - "ControllerSettingsDPadDown": "Dół", - "ControllerSettingsDPadLeft": "Lewo", - "ControllerSettingsDPadRight": "Prawo", - "ControllerSettingsStickButton": "Przycisk", - "ControllerSettingsStickUp": "Góra ", - "ControllerSettingsStickDown": "Dół ", - "ControllerSettingsStickLeft": "Lewo", - "ControllerSettingsStickRight": "Prawo", - "ControllerSettingsStickStick": "Gałka", - "ControllerSettingsStickInvertXAxis": "Odwróć gałkę X", - "ControllerSettingsStickInvertYAxis": "Odwróć gałkę Y", - "ControllerSettingsStickDeadzone": "Martwa strefa:", - "ControllerSettingsLStick": "Lewa Gałka", - "ControllerSettingsRStick": "Prawa Gałka", - "ControllerSettingsTriggersLeft": "Lewe Triggery", - "ControllerSettingsTriggersRight": "Prawe Triggery", - "ControllerSettingsTriggersButtonsLeft": "Lewe Przyciski Triggerów", - "ControllerSettingsTriggersButtonsRight": "Prawe Przyciski Triggerów", - "ControllerSettingsTriggers": "Triggery", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Lewe Przyciski", - "ControllerSettingsExtraButtonsRight": "Prawe Przyciski", - "ControllerSettingsMisc": "Różne", - "ControllerSettingsTriggerThreshold": "Próg Triggerów:", - "ControllerSettingsMotion": "Ruch", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Użyj ruchu zgodnego z CemuHook", - "ControllerSettingsMotionControllerSlot": "Slot Kontrolera:", - "ControllerSettingsMotionMirrorInput": "Odzwierciedlaj Sterowanie", - "ControllerSettingsMotionRightJoyConSlot": "Prawy Slot JoyCon:", - "ControllerSettingsMotionServerHost": "Host Serwera:", - "ControllerSettingsMotionGyroSensitivity": "Czułość Żyroskopu:", - "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", - "ControllerSettingsSave": "Zapisz", - "ControllerSettingsClose": "Zamknij", - "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", - "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", - "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", - "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", - "UserProfilesAddNewProfile": "Utwórz Profil", - "UserProfilesDelete": "Usuwać", - "UserProfilesClose": "Zamknij", - "ProfileNameSelectionWatermark": "Wybierz pseudonim", - "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", - "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", - "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", - "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", - "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", - "InputDialogTitle": "Okno Dialogowe Wprowadzania", - "InputDialogOk": "OK", - "InputDialogCancel": "Anuluj", - "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", - "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", - "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", - "AvatarChoose": "Wybierz", - "AvatarSetBackgroundColor": "Ustaw Kolor Tła", - "AvatarClose": "Zamknij", - "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", - "ControllerSettingsAddProfileToolTip": "Dodaj Profil", - "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", - "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", - "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", - "MenuBarFileToolsHideUi": "Ukryj UI", - "GameListContextMenuRunApplication": "Uruchom aplikację ", - "GameListContextMenuToggleFavorite": "Przełącz Ulubione", - "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", - "SettingsTabGeneralTheme": "Motyw", - "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", - "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", - "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", - "SettingsTabGeneralThemeBaseStyleLight": "Jasny", - "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", - "ButtonBrowse": "Przeglądaj", - "ControllerSettingsConfigureGeneral": "Konfiguruj", - "ControllerSettingsRumble": "Wibracje", - "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", - "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", - "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", - "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", - "DialogUpdaterTitle": "Ryujinx - Aktualizator", - "DialogErrorTitle": "Ryujinx - Błąd", - "DialogWarningTitle": "Ryujinx - Uwaga", - "DialogExitTitle": "Ryujinx - Wyjdź", - "DialogErrorMessage": "Ryujinx napotkał błąd", - "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", - "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", - "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", - "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", - "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", - "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", - "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", - "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", - "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", - "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", - "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", - "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", - "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", - "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", - "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", - "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", - "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", - "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", - "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", - "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Pobierz Ryujinx ze strony https://ryujinx.org/, jeśli szukasz obsługiwanej wersji.", - "DialogRestartRequiredMessage": "Wymagane Ponowne Uruchomienie", - "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", - "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", - "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", - "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", - "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", - "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", - "DialogInstallFileTypesErrorMessage": "Nie udało się zainstalować typów plików.", - "DialogUninstallFileTypesSuccessMessage": "Pomyślnie odinstalowano typy plików!", - "DialogUninstallFileTypesErrorMessage": "Nie udało się odinstalować typów plików.", - "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", - "DialogControllerAppletTitle": "Aplet Kontrolera", - "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", - "DialogErrorAppletErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nAby uzyskać więcej informacji o tym, jak naprawić ten błąd, zapoznaj się z naszym Przewodnikiem instalacji.", - "DialogUserErrorDialogTitle": "Błąd Ryujinxa ({0})", - "DialogAmiiboApiTitle": "API Amiibo", - "DialogAmiiboApiFailFetchMessage": "Wystąpił błąd podczas pobierania informacji z API.", - "DialogAmiiboApiConnectErrorMessage": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.", - "DialogProfileInvalidProfileErrorMessage": "Profil {0} jest niezgodny z bieżącym systemem konfiguracji sterowania.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Profil Domyślny nie może zostać nadpisany", - "DialogProfileDeleteProfileTitle": "Usuwanie Profilu", - "DialogProfileDeleteProfileMessage": "Ta czynność jest nieodwracalna, czy na pewno chcesz kontynuować?", - "DialogWarning": "Uwaga", - "DialogPPTCDeletionMessage": "Masz zamiar umieścić w kolejce rekompilację PPTC przy następnym uruchomieniu:\n\n{0}\n\nCzy na pewno chcesz kontynuować?", - "DialogPPTCDeletionErrorMessage": "Błąd czyszczenia cache PPTC w {0}: {1}", - "DialogShaderDeletionMessage": "Zamierzasz usunąć cache Shaderów dla :\n\n{0}\n\nNa pewno chcesz kontynuować?", - "DialogShaderDeletionErrorMessage": "Błąd czyszczenia cache Shaderów w {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx napotkał błąd", - "DialogInvalidTitleIdErrorMessage": "Błąd UI: Wybrana gra nie miała prawidłowego ID tytułu", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Zainstaluj Firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Wersja systemu {0} zostanie zainstalowana.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nZastąpi to obecną wersję systemu {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nCzy chcesz kontynuować?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalowanie firmware'u...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Wersja systemu {0} została pomyślnie zainstalowana.", - "DialogUserProfileDeletionWarningMessage": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty", - "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", - "DialogUserProfileUnsavedChangesTitle": "Uwaga - Niezapisane zmiany", - "DialogUserProfileUnsavedChangesMessage": "Wprowadziłeś zmiany dla tego profilu użytkownika, które nie zostały zapisane.", - "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", - "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", - "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", - "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", - "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Masz włączone zrzucanie shaderów, które jest przeznaczone tylko dla programistów.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie zrzucania shaderów. Czy chcesz teraz wyłączyć zrzucanie shaderów?", - "DialogLoadAppGameAlreadyLoadedMessage": "Gra została już załadowana", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Zatrzymaj emulację lub zamknij emulator przed uruchomieniem innej gry.", - "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", - "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Funkcje", - "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", - "CommonAuto": "Auto", - "CommonOff": "Wyłączone", - "CommonOn": "Włączone", - "InputDialogYes": "Tak", - "InputDialogNo": "Nie", - "DialogProfileInvalidProfileNameErrorMessage": "Nazwa pliku zawiera nieprawidłowe znaki. Proszę spróbuj ponownie.", - "MenuBarOptionsPauseEmulation": "Pauza", - "MenuBarOptionsResumeEmulation": "Wznów", - "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", - "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", - "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", - "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", - "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", - "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", - "AboutRyujinxAboutTitle": "O Aplikacji:", - "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", - "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", - "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", - "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", - "AmiiboSeriesLabel": "Seria Amiibo", - "AmiiboCharacterLabel": "Postać", - "AmiiboScanButtonLabel": "Zeskanuj", - "AmiiboOptionsShowAllLabel": "Pokaż Wszystkie Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Użyj losowego UUID tagu", - "DlcManagerTableHeadingEnabledLabel": "Włączone", - "DlcManagerTableHeadingTitleIdLabel": "ID Tytułu", - "DlcManagerTableHeadingContainerPathLabel": "Ścieżka Kontenera", - "DlcManagerTableHeadingFullPathLabel": "Pełna Ścieżka", - "DlcManagerRemoveAllButton": "Usuń Wszystkie", - "DlcManagerEnableAllButton": "Włącz Wszystkie", - "DlcManagerDisableAllButton": "Wyłącz Wszystkie", - "MenuBarOptionsChangeLanguage": "Zmień Język", - "MenuBarShowFileTypes": "Pokaż typy plików", - "CommonSort": "Sortuj", - "CommonShowNames": "Pokaż Nazwy", - "CommonFavorite": "Ulubione", - "OrderAscending": "Rosnąco", - "OrderDescending": "Malejąco", - "SettingsTabGraphicsFeatures": "Funkcje i Ulepszenia", - "ErrorWindowTitle": "Okno Błędu", - "ToggleDiscordTooltip": "Wybierz, czy chcesz wyświetlać Ryujinx w swojej \"aktualnie grane\" aktywności Discord", - "AddGameDirBoxTooltip": "Wprowadź katalog gier aby dodać go do listy", - "AddGameDirTooltip": "Dodaj katalog gier do listy", - "RemoveGameDirTooltip": "Usuń wybrany katalog gier", - "CustomThemeCheckTooltip": "Użyj niestandardowego motywu Avalonia dla GUI, aby zmienić wygląd menu emulatora", - "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", - "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", - "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", - "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", - "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", - "RegionTooltip": "Zmień Region Systemu", - "LanguageTooltip": "Zmień Język Systemu", - "TimezoneTooltip": "Zmień Strefę Czasową Systemu", - "TimeTooltip": "Zmień Czas Systemu", - "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", - "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", - "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", - "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", - "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", - "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", - "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", - "MemoryManagerUnsafeTooltip": "Bezpośrednio mapuj pamięć, ale nie maskuj adresu w przestrzeni adresowej gościa przed uzyskaniem dostępu. Szybciej, ale kosztem bezpieczeństwa. Aplikacja gościa może uzyskać dostęp do pamięci z dowolnego miejsca w Ryujinx, więc w tym trybie uruchamiaj tylko programy, którym ufasz.", - "UseHypervisorTooltip": "Użyj Hiperwizora zamiast JIT. Znacznie poprawia wydajność, gdy jest dostępny, ale może być niestabilny w swoim obecnym stanie ", - "DRamTooltip": "Wykorzystuje alternatywny układ MemoryMode, aby naśladować model rozwojowy Switcha.\n\nJest to przydatne tylko w przypadku pakietów tekstur o wyższej rozdzielczości lub modów w rozdzielczości 4k. NIE poprawia wydajności.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", - "IgnoreMissingServicesTooltip": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", - "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", - "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", - "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", - "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", - "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", - "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", - "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", - "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", - "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", - "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", - "InfoLogTooltip": "Wyświetla komunikaty dziennika informacyjnego w konsoli. Nie wpływa na wydajność.", - "WarnLogTooltip": "Wyświetla komunikaty dziennika ostrzeżeń w konsoli. Nie wpływa na wydajność.", - "ErrorLogTooltip": "Wyświetla w konsoli komunikaty dziennika błędów. Nie wpływa na wydajność.", - "TraceLogTooltip": "Wyświetla komunikaty dziennika śledzenia w konsoli. Nie wpływa na wydajność.", - "GuestLogTooltip": "Wyświetla komunikaty dziennika gości w konsoli. Nie wpływa na wydajność.", - "FileAccessLogTooltip": "Wyświetla w konsoli komunikaty dziennika dostępu do plików.", - "FSAccessLogModeTooltip": "Włącza wyjście dziennika dostępu FS do konsoli. Możliwe tryby to 0-3", - "DeveloperOptionTooltip": "Używaj ostrożnie", - "OpenGlLogLevel": "Wymaga włączonych odpowiednich poziomów logów", - "DebugLogTooltip": "Wyświetla komunikaty dziennika debugowania w konsoli.\n\nUżywaj tego tylko na wyraźne polecenie członka załogi, ponieważ utrudni to odczytanie dzienników i pogorszy wydajność emulatora.", - "LoadApplicationFileTooltip": "Otwórz eksplorator plików, aby wybrać plik kompatybilny z Switch do wczytania", - "LoadApplicationFolderTooltip": "Otwórz eksplorator plików, aby wybrać zgodną z Switch, rozpakowaną aplikację do załadowania", - "OpenRyujinxFolderTooltip": "Otwórz folder systemu plików Ryujinx", - "OpenRyujinxLogsTooltip": "Otwiera folder, w którym zapisywane są logi", - "ExitTooltip": "Wyjdź z Ryujinx", - "OpenSettingsTooltip": "Otwórz okno ustawień", - "OpenProfileManagerTooltip": "Otwórz okno Menedżera Profili Użytkownika", - "StopEmulationTooltip": "Zatrzymaj emulację bieżącej gry i wróć do wyboru gier", - "CheckUpdatesTooltip": "Sprawdź aktualizacje Ryujinx", - "OpenAboutTooltip": "Otwórz Okno Informacje", - "GridSize": "Wielkość siatki", - "GridSizeTooltip": "Zmień rozmiar elementów siatki", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazylijski Portugalski", - "AboutRyujinxContributorsButtonHeader": "Zobacz Wszystkich Współtwórców", - "SettingsTabSystemAudioVolume": "Głośność: ", - "AudioVolumeTooltip": "Zmień Głośność Dźwięku", - "SettingsTabSystemEnableInternetAccess": "Dostęp do Internetu Gościa/Tryb LAN", - "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", - "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", - "GameListContextMenuManageCheat": "Zarządzaj Kodami", - "ControllerSettingsStickRange": "Zasięg:", - "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", - "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Dżwięk", - "SettingsTabNetwork": "Sieć", - "SettingsTabNetworkConnection": "Połączenie Sieciowe", - "SettingsTabCpuCache": "Cache CPU", - "SettingsTabCpuMemory": "Pamięć CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", - "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", - "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", - "ControllerSettingsRotate90": "Obróć o 90° w Prawo", - "IconSize": "Rozmiar Ikon", - "IconSizeTooltip": "Zmień rozmiar ikon gry", - "MenuBarOptionsShowConsole": "Pokaż Konsolę", - "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", - "UserErrorNoKeys": "Nie znaleziono kluczy", - "UserErrorNoFirmware": "Nie znaleziono firmware'u", - "UserErrorFirmwareParsingFailed": "Błąd parsowania firmware'u", - "UserErrorApplicationNotFound": "Aplikacja nie znaleziona", - "UserErrorUnknown": "Nieznany błąd", - "UserErrorUndefined": "Niezdefiniowany błąd", - "UserErrorNoKeysDescription": "Ryujinx nie mógł znaleźć twojego pliku 'prod.keys'", - "UserErrorNoFirmwareDescription": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.", - "UserErrorApplicationNotFoundDescription": "Ryujinx nie mógł znaleźć prawidłowej aplikacji na podanej ścieżce.", - "UserErrorUnknownDescription": "Wystąpił nieznany błąd!", - "UserErrorUndefinedDescription": "Wystąpił niezdefiniowany błąd! To nie powinno się zdarzyć, skontaktuj się z deweloperem!", - "OpenSetupGuideMessage": "Otwórz Podręcznik Konfiguracji", - "NoUpdate": "Brak Aktualizacji", - "TitleUpdateVersionLabel": "Wersja {0} - {1}", - "RyujinxInfo": "Ryujinx - Info", - "RyujinxConfirm": "Ryujinx - Potwierdzenie", - "FileDialogAllTypes": "Wszystkie typy", - "Never": "Nigdy", - "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", - "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", - "SoftwareKeyboard": "Klawiatura Oprogramowania", - "SoftwareKeyboardModeNumbersOnly": "Mogą być tylko liczby ", - "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", - "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", - "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", - "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", - "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", - "UpdaterAddingFiles": "Dodawanie Nowych Plików...", - "UpdaterExtracting": "Wypakowywanie Aktualizacji...", - "UpdaterDownloading": "Pobieranie Aktualizacji...", - "Game": "Gra", - "Docked": "Zadokowany", - "Handheld": "Przenośny", - "ConnectionError": "Błąd Połączenia.", - "AboutPageDeveloperListMore": "{0} i więcej...", - "ApiError": "Błąd API.", - "LoadingHeading": "Wczytywanie {0}", - "CompilingPPTC": "Kompilowanie PTC", - "CompilingShaders": "Kompilowanie Shaderów", - "AllKeyboards": "Wszystkie klawiatury", - "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", - "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", - "AllSupportedFormats": "Wszystkie Obsługiwane Formaty", - "RyujinxUpdater": "Aktualizator Ryujinx", - "SettingsTabHotkeys": "Skróty Klawiszowe Klawiatury", - "SettingsTabHotkeysHotkeys": "Skróty Klawiszowe Klawiatury", - "SettingsTabHotkeysToggleVsyncHotkey": "Przełącz VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Zrzut Ekranu:", - "SettingsTabHotkeysShowUiHotkey": "Pokaż UI:", - "SettingsTabHotkeysPauseHotkey": "Pauza:", - "SettingsTabHotkeysToggleMuteHotkey": "Wycisz:", - "ControllerMotionTitle": "Ustawienia Sterowania Ruchowego", - "ControllerRumbleTitle": "Ustawienia Wibracji", - "SettingsSelectThemeFileDialogTitle": "Wybierz Plik Motywu", - "SettingsXamlThemeFile": "Plik Motywu Xaml", - "AvatarWindowTitle": "Zarządzaj Kontami — Avatar", - "Amiibo": "Amiibo", - "Unknown": "Nieznane", - "Usage": "Użycie", - "Writable": "Zapisywalne", - "SelectDlcDialogTitle": "Wybierz pliki DLC", - "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", - "UserProfileWindowTitle": "Menedżer Profili Użytkowników", - "CheatWindowTitle": "Menedżer Kodów", - "DlcWindowTitle": "Menedżer Zawartości do Pobrania", - "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", - "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", - "BuildId": "Identyfikator wersji:", - "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", - "UserProfilesEditProfile": "Edytuj Zaznaczone", - "Cancel": "Anuluj", - "Save": "Zapisz", - "Discard": "Odrzuć", - "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", - "UserProfileEmptyNameError": "Nazwa jest wymagana", - "UserProfileNoImageError": "Należy ustawić obraz profilowy", - "GameUpdateWindowHeading": "{0} Aktualizacje dostępne dla {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", - "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", - "UserProfilesName": "Nazwa:", - "UserProfilesUserId": "ID Użytkownika:", - "SettingsTabGraphicsBackend": "Backend Graficzny", - "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", - "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", - "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", - "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", - "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", - "SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia", - "SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?", - "RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?", - "SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:", - "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", - "SettingsEnableMacroHLE": "Włącz Macro HLE", - "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Głoś", - "UserProfilesManageSaves": "Zarządzaj Zapisami", - "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", - "IrreversibleActionNote": "Ta czynność nie jest odwracalna.", - "SaveManagerHeading": "Zarządzaj Zapisami dla {0}", - "SaveManagerTitle": "Menedżer Zapisów", - "Name": "Nazwa", - "Size": "Rozmiar", - "Search": "Wyszukaj", - "UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta", - "Recover": "Odzyskaj", - "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", - "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", - "GraphicsAATooltip": "Stosuje antyaliasing do renderowania gry", - "GraphicsAALabel": "Antyaliasing:", - "GraphicsScalingFilterLabel": "Filtr skalowania:", - "GraphicsScalingFilterTooltip": "Włącza skalowanie bufora ramki", - "GraphicsScalingFilterLevelLabel": "Poziom", - "GraphicsScalingFilterLevelTooltip": "Ustaw poziom filtra skalowania", - "SmaaLow": "SMAA Niskie", - "SmaaMedium": "SMAA Średnie", - "SmaaHigh": "SMAA Wysokie", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Edytuj użytkownika", - "UserEditorTitleCreate": "Utwórz użytkownika", - "SettingsTabNetworkInterface": "Interfejs sieci:", - "NetworkInterfaceTooltip": "Interfejs sieciowy używany do funkcji LAN", - "NetworkInterfaceDefault": "Domyślny", - "PackagingShaders": "Pakuje Shadery ", - "AboutChangelogButton": "Zobacz listę zmian na GitHubie", - "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/pt_BR.json b/src/Ryujinx.Ava/Assets/Locales/pt_BR.json deleted file mode 100644 index 8909a84f..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/pt_BR.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Português (BR)", - "MenuBarFileOpenApplet": "Abrir Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", - "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", - "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", - "SettingsTabSystemMemoryManagerModeSoftware": "Software", - "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", - "SettingsTabSystemUseHypervisor": "Usar Hipervisor", - "MenuBarFile": "_Arquivo", - "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", - "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", - "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", - "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", - "MenuBarFileExit": "_Sair", - "MenuBarOptions": "_Opções", - "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", - "MenuBarOptionsStartGamesInFullscreen": "Iniciar jogos em tela cheia", - "MenuBarOptionsStopEmulation": "_Encerrar emulação", - "MenuBarOptionsSettings": "_Configurações", - "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", - "MenuBarActions": "_Ações", - "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", - "MenuBarActionsScanAmiibo": "Escanear um Amiibo", - "MenuBarTools": "_Ferramentas", - "MenuBarToolsInstallFirmware": "_Instalar firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", - "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", - "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", - "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", - "MenuBarHelp": "A_juda", - "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", - "MenuBarHelpAbout": "_Sobre", - "MenuSearch": "Buscar...", - "GameListHeaderFavorite": "Favorito", - "GameListHeaderIcon": "Ícone", - "GameListHeaderApplication": "Nome", - "GameListHeaderDeveloper": "Desenvolvedor", - "GameListHeaderVersion": "Versão", - "GameListHeaderTimePlayed": "Tempo de jogo", - "GameListHeaderLastPlayed": "Último jogo", - "GameListHeaderFileExtension": "Extensão", - "GameListHeaderFileSize": "Tamanho", - "GameListHeaderPath": "Caminho", - "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", - "GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", - "GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", - "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", - "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", - "GameListContextMenuManageDlc": "Gerenciar DLCs", - "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", - "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", - "GameListContextMenuCacheManagement": "Gerenciamento de cache", - "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", - "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", - "GameListContextMenuExtractData": "Extrair dados", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", - "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", - "StatusBarGamesLoaded": "{0}/{1} jogos carregados", - "StatusBarSystemVersion": "Versão do firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", - "Settings": "Configurações", - "SettingsTabGeneral": "Geral", - "SettingsTabGeneralGeneral": "Geral", - "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", - "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", - "SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:", - "SettingsTabGeneralHideCursorNever": "Nunca", - "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", - "SettingsTabGeneralHideCursorAlways": "Sempre", - "SettingsTabGeneralGameDirectories": "Diretórios de jogo", - "SettingsTabGeneralAdd": "Adicionar", - "SettingsTabGeneralRemove": "Remover", - "SettingsTabSystem": "Sistema", - "SettingsTabSystemCore": "Principal", - "SettingsTabSystemSystemRegion": "Região do sistema:", - "SettingsTabSystemSystemRegionJapan": "Japão", - "SettingsTabSystemSystemRegionUSA": "EUA", - "SettingsTabSystemSystemRegionEurope": "Europa", - "SettingsTabSystemSystemRegionAustralia": "Austrália", - "SettingsTabSystemSystemRegionChina": "China", - "SettingsTabSystemSystemRegionKorea": "Coreia", - "SettingsTabSystemSystemRegionTaiwan": "Taiwan", - "SettingsTabSystemSystemLanguage": "Idioma do sistema:", - "SettingsTabSystemSystemLanguageJapanese": "Japonês", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", - "SettingsTabSystemSystemLanguageFrench": "Francês", - "SettingsTabSystemSystemLanguageGerman": "Alemão", - "SettingsTabSystemSystemLanguageItalian": "Italiano", - "SettingsTabSystemSystemLanguageSpanish": "Espanhol", - "SettingsTabSystemSystemLanguageChinese": "Chinês", - "SettingsTabSystemSystemLanguageKorean": "Coreano", - "SettingsTabSystemSystemLanguageDutch": "Holandês", - "SettingsTabSystemSystemLanguagePortuguese": "Português", - "SettingsTabSystemSystemLanguageRussian": "Russo", - "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", - "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", - "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", - "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", - "SettingsTabSystemSystemTime": "Hora do sistema:", - "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", - "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", - "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", - "SettingsTabSystemAudioBackendDummy": "Nenhuma", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Pode causar instabilidade)", - "SettingsTabSystemExpandDramSize": "Expandir memória para 6GiB", - "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", - "SettingsTabGraphics": "Gráficos", - "SettingsTabGraphicsAPI": "API gráfica", - "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", - "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Escala de resolução:", - "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", - "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Proporção:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", - "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", - "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", - "SettingsTabLogging": "Log", - "SettingsTabLoggingLogging": "Log", - "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", - "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", - "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", - "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", - "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", - "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", - "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", - "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", - "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", - "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", - "SettingsTabLoggingDeveloperOptionsNote": "AVISO: Reduzirá o desempenho", - "SettingsTabLoggingGraphicsBackendLogLevel": "Nível de log do backend gráfico:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Erro", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos", - "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", - "SettingsTabInput": "Controle", - "SettingsTabInputEnableDockedMode": "Habilitar modo TV", - "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", - "SettingsButtonSave": "Salvar", - "SettingsButtonClose": "Fechar", - "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Cancelar", - "SettingsButtonApply": "Aplicar", - "ControllerSettingsPlayer": "Jogador", - "ControllerSettingsPlayer1": "Jogador 1", - "ControllerSettingsPlayer2": "Jogador 2", - "ControllerSettingsPlayer3": "Jogador 3", - "ControllerSettingsPlayer4": "Jogador 4", - "ControllerSettingsPlayer5": "Jogador 5", - "ControllerSettingsPlayer6": "Jogador 6", - "ControllerSettingsPlayer7": "Jogador 7", - "ControllerSettingsPlayer8": "Jogador 8", - "ControllerSettingsHandheld": "Portátil", - "ControllerSettingsInputDevice": "Dispositivo de entrada", - "ControllerSettingsRefresh": "Atualizar", - "ControllerSettingsDeviceDisabled": "Desabilitado", - "ControllerSettingsControllerType": "Tipo do controle", - "ControllerSettingsControllerTypeHandheld": "Portátil", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", - "ControllerSettingsProfile": "Perfil", - "ControllerSettingsProfileDefault": "Padrão", - "ControllerSettingsLoad": "Carregar", - "ControllerSettingsAdd": "Adicionar", - "ControllerSettingsRemove": "Remover", - "ControllerSettingsButtons": "Botões", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Direcional", - "ControllerSettingsDPadUp": "Cima", - "ControllerSettingsDPadDown": "Baixo", - "ControllerSettingsDPadLeft": "Esquerda", - "ControllerSettingsDPadRight": "Direita", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", - "ControllerSettingsLStick": "Analógico esquerdo", - "ControllerSettingsRStick": "Analógico direito", - "ControllerSettingsTriggersLeft": "Gatilhos esquerda", - "ControllerSettingsTriggersRight": "Gatilhos direita", - "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", - "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", - "ControllerSettingsTriggers": "Gatilhos", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Botões esquerda", - "ControllerSettingsExtraButtonsRight": "Botões direita", - "ControllerSettingsMisc": "Miscelâneas", - "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", - "ControllerSettingsMotion": "Sensor de movimento", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", - "ControllerSettingsMotionControllerSlot": "Slot do controle:", - "ControllerSettingsMotionMirrorInput": "Espelhar movimento", - "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", - "ControllerSettingsMotionServerHost": "Endereço do servidor:", - "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", - "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", - "ControllerSettingsSave": "Salvar", - "ControllerSettingsClose": "Fechar", - "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", - "UserProfilesSaveProfileName": "Salvar nome de perfil", - "UserProfilesChangeProfileImage": "Mudar imagem de perfil", - "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", - "UserProfilesAddNewProfile": "Adicionar novo perfil", - "UserProfilesDelete": "Apagar", - "UserProfilesClose": "Fechar", - "ProfileNameSelectionWatermark": "Escolha um apelido", - "ProfileImageSelectionTitle": "Seleção da imagem de perfil", - "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", - "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", - "ProfileImageSelectionImportImage": "Importar arquivo de imagem", - "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", - "InputDialogTitle": "Diálogo de texto", - "InputDialogOk": "OK", - "InputDialogCancel": "Cancelar", - "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", - "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", - "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", - "AvatarChoose": "Escolher", - "AvatarSetBackgroundColor": "Definir cor de fundo", - "AvatarClose": "Fechar", - "ControllerSettingsLoadProfileToolTip": "Carregar perfil", - "ControllerSettingsAddProfileToolTip": "Adicionar perfil", - "ControllerSettingsRemoveProfileToolTip": "Remover perfil", - "ControllerSettingsSaveProfileToolTip": "Salvar perfil", - "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", - "MenuBarFileToolsHideUi": "Esconder Interface", - "GameListContextMenuRunApplication": "Run Application", - "GameListContextMenuToggleFavorite": "Alternar favorito", - "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Escuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", - "ButtonBrowse": "Procurar", - "ControllerSettingsConfigureGeneral": "Configurar", - "ControllerSettingsRumble": "Vibração", - "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", - "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", - "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", - "DialogConfirmationTitle": "Ryujinx - Confirmação", - "DialogUpdaterTitle": "Ryujinx - Atualizador", - "DialogErrorTitle": "Ryujinx - Erro", - "DialogWarningTitle": "Ryujinx - Alerta", - "DialogExitTitle": "Ryujinx - Sair", - "DialogErrorMessage": "Ryujinx encontrou um erro", - "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", - "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", - "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", - "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", - "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", - "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", - "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", - "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", - "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter as informações de atualização do GitHub Release. Isso pode ser causado se uma nova versão estiver sendo compilado pelas Ações do GitHub. Tente novamente em alguns minutos.", - "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", - "DialogUpdaterDownloadingMessage": "Baixando atualização...", - "DialogUpdaterExtractionMessage": "Extraindo atualização...", - "DialogUpdaterRenamingMessage": "Renomeando atualização...", - "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", - "DialogUpdaterCompleteMessage": "Atualização concluída!", - "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", - "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", - "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", - "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", - "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", - "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", - "DialogRestartRequiredMessage": "Reinicialização necessária", - "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", - "DialogThemeRestartSubMessage": "Deseja reiniciar?", - "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", - "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", - "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", - "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", - "DialogInstallFileTypesErrorMessage": "Falha ao instalar tipos de arquivo.", - "DialogUninstallFileTypesSuccessMessage": "Tipos de arquivo desinstalados com sucesso!", - "DialogUninstallFileTypesErrorMessage": "Falha ao desinstalar tipos de arquivo.", - "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", - "DialogControllerAppletTitle": "Applet de controle", - "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", - "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", - "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", - "DialogAmiiboApiTitle": "API Amiibo", - "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", - "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", - "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", - "DialogProfileDeleteProfileTitle": "Apagando perfil", - "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", - "DialogWarning": "Alerta", - "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", - "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", - "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", - "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", - "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", - "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", - "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", - "DialogUserProfileUnsavedChangesTitle": "Alerta - Alterações não salvas", - "DialogUserProfileUnsavedChangesMessage": "Você fez alterações para este perfil de usuário que não foram salvas.", - "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", - "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", - "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", - "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", - "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", - "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", - "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", - "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Recursos", - "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", - "CommonAuto": "Automático", - "CommonOff": "Desligado", - "CommonOn": "Ligado", - "InputDialogYes": "Sim", - "InputDialogNo": "Não", - "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", - "MenuBarOptionsPauseEmulation": "Pausar", - "MenuBarOptionsResumeEmulation": "Resumir", - "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", - "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", - "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", - "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", - "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", - "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", - "AboutRyujinxAboutTitle": "Sobre:", - "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", - "AboutRyujinxMaintainersTitle": "Mantido por:", - "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", - "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", - "AmiiboSeriesLabel": "Franquia Amiibo", - "AmiiboCharacterLabel": "Personagem", - "AmiiboScanButtonLabel": "Escanear", - "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", - "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", - "DlcManagerTableHeadingEnabledLabel": "Habilitado", - "DlcManagerTableHeadingTitleIdLabel": "ID do título", - "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", - "DlcManagerTableHeadingFullPathLabel": "Caminho completo", - "DlcManagerRemoveAllButton": "Remover todos", - "DlcManagerEnableAllButton": "Habilitar todos", - "DlcManagerDisableAllButton": "Desabilitar todos", - "MenuBarOptionsChangeLanguage": "Mudar idioma", - "MenuBarShowFileTypes": "Mostrar tipos de arquivo", - "CommonSort": "Ordenar", - "CommonShowNames": "Exibir nomes", - "CommonFavorite": "Favorito", - "OrderAscending": "Ascendente", - "OrderDescending": "Descendente", - "SettingsTabGraphicsFeatures": "Recursos & Melhorias", - "ErrorWindowTitle": "Janela de erro", - "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", - "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", - "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", - "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", - "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", - "CustomThemePathTooltip": "Diretório do tema customizado", - "CustomThemeBrowseTooltip": "Navegar até um tema customizado", - "DockModeToggleTooltip": "Habilita ou desabilita modo TV", - "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", - "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", - "RegionTooltip": "Mudar a região do sistema", - "LanguageTooltip": "Mudar o idioma do sistema", - "TimezoneTooltip": "Mudar o fuso-horário do sistema", - "TimeTooltip": "Mudar a hora do sistema", - "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", - "PptcToggleTooltip": "Habilita ou desabilita PPTC", - "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", - "AudioBackendTooltip": "Mudar biblioteca de áudio", - "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", - "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", - "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", - "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", - "UseHypervisorTooltip": "Usa o Hypervisor em vez de JIT (recompilador dinâmico). Melhora significativamente o desempenho quando disponível, mas pode ser instável no seu estado atual.", - "DRamTooltip": "Expande a memória do sistema emulado de 4GiB para 6GiB", - "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", - "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", - "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", - "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", - "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", - "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", - "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", - "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", - "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", - "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", - "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", - "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", - "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", - "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", - "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", - "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", - "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", - "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", - "DeveloperOptionTooltip": "Use com cuidado", - "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", - "DebugLogTooltip": "Habilita exibição de mensagens de depuração", - "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", - "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", - "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", - "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", - "ExitTooltip": "Sair do Ryujinx", - "OpenSettingsTooltip": "Abrir janela de configurações", - "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", - "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", - "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", - "OpenAboutTooltip": "Abrir janela sobre", - "GridSize": "Tamanho da grade", - "GridSizeTooltip": "Mudar tamanho dos items da grade", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", - "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", - "SettingsTabSystemAudioVolume": "Volume:", - "AudioVolumeTooltip": "Mudar volume do áudio", - "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", - "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", - "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", - "GameListContextMenuManageCheat": "Gerenciar Cheats", - "ControllerSettingsStickRange": "Intervalo:", - "DialogStopEmulationTitle": "Ryujinx - Parar emulação", - "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "Áudio", - "SettingsTabNetwork": "Rede", - "SettingsTabNetworkConnection": "Conexão de rede", - "SettingsTabCpuCache": "Cache da CPU", - "SettingsTabCpuMemory": "Memória da CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", - "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", - "ControllerSettingsRotate90": "Rodar 90° sentido horário", - "IconSize": "Tamanho do ícone", - "IconSizeTooltip": "Muda o tamanho do ícone do jogo", - "MenuBarOptionsShowConsole": "Exibir console", - "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", - "UserErrorNoKeys": "Chaves não encontradas", - "UserErrorNoFirmware": "Firmware não encontrado", - "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", - "UserErrorApplicationNotFound": "Aplicativo não encontrado", - "UserErrorUnknown": "Erro desconhecido", - "UserErrorUndefined": "Erro indefinido", - "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", - "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", - "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", - "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", - "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", - "OpenSetupGuideMessage": "Abrir o guia de configuração", - "NoUpdate": "Sem atualizações", - "TitleUpdateVersionLabel": "Versão {0} - {1}", - "RyujinxInfo": "Ryujinx - Informação", - "RyujinxConfirm": "Ryujinx - Confirmação", - "FileDialogAllTypes": "Todos os tipos", - "Never": "Nunca", - "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", - "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", - "SoftwareKeyboard": "Teclado por Software", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", - "UpdaterRenaming": "Renomeando arquivos antigos...", - "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", - "UpdaterAddingFiles": "Adicionando novos arquivos...", - "UpdaterExtracting": "Extraíndo atualização...", - "UpdaterDownloading": "Baixando atualização...", - "Game": "Jogo", - "Docked": "TV", - "Handheld": "Portátil", - "ConnectionError": "Erro de conexão.", - "AboutPageDeveloperListMore": "{0} e mais...", - "ApiError": "Erro de API.", - "LoadingHeading": "Carregando {0}", - "CompilingPPTC": "Compilando PTC", - "CompilingShaders": "Compilando Shaders", - "AllKeyboards": "Todos os teclados", - "OpenFileDialogTitle": "Selecione um arquivo suportado para abrir", - "OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído", - "AllSupportedFormats": "Todos os formatos suportados", - "RyujinxUpdater": "Atualizador do Ryujinx", - "SettingsTabHotkeys": "Atalhos do teclado", - "SettingsTabHotkeysHotkeys": "Atalhos do teclado", - "SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Captura de tela:", - "SettingsTabHotkeysShowUiHotkey": "Exibir UI:", - "SettingsTabHotkeysPauseHotkey": "Pausar:", - "SettingsTabHotkeysToggleMuteHotkey": "Mudo:", - "ControllerMotionTitle": "Configurações do controle de movimento", - "ControllerRumbleTitle": "Configurações de vibração", - "SettingsSelectThemeFileDialogTitle": "Selecionar arquivo do tema", - "SettingsXamlThemeFile": "Arquivo de tema Xaml", - "AvatarWindowTitle": "Gerenciar contas - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Desconhecido", - "Usage": "Uso", - "Writable": "Gravável", - "SelectDlcDialogTitle": "Selecionar arquivos de DLC", - "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", - "UserProfileWindowTitle": "Gerenciador de perfis de usuário", - "CheatWindowTitle": "Gerenciador de Cheats", - "DlcWindowTitle": "Gerenciador de DLC", - "UpdateWindowTitle": "Gerenciador de atualizações", - "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", - "UserProfilesEditProfile": "Editar selecionado", - "Cancel": "Cancelar", - "Save": "Salvar", - "Discard": "Descartar", - "UserProfilesSetProfileImage": "Definir imagem de perfil", - "UserProfileEmptyNameError": "É necessário um nome", - "UserProfileNoImageError": "A imagem de perfil deve ser definida", - "GameUpdateWindowHeading": "{0} atualizações disponíveis para {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:", - "SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:", - "UserProfilesName": "Nome:", - "UserProfilesUserId": "ID de usuário:", - "SettingsTabGraphicsBackend": "Backend gráfico", - "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", - "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", - "SettingsTabGraphicsPreferredGpu": "GPU preferencial", - "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", - "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", - "SettingsGpuBackendRestartMessage": "Configurações do backend gráfico ou da GPU foram alteradas. Uma reinicialização é necessária para que as mudanças tenham efeito.", - "SettingsGpuBackendRestartSubMessage": "Deseja reiniciar agora?", - "RyujinxUpdaterMessage": "Você quer atualizar o Ryujinx para a última versão?", - "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volume:", - "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", - "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", - "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Vol", - "UserProfilesManageSaves": "Gerenciar jogos salvos", - "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", - "IrreversibleActionNote": "Esta ação não é reversível.", - "SaveManagerHeading": "Gerenciar jogos salvos para {0}", - "SaveManagerTitle": "Gerenciador de jogos salvos", - "Name": "Nome", - "Size": "Tamanho", - "Search": "Buscar", - "UserProfilesRecoverLostAccounts": "Recuperar contas perdidas", - "Recover": "Recuperar", - "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", - "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", - "GraphicsAATooltip": "Aplica anti-serrilhamento à renderização do jogo", - "GraphicsAALabel": "Anti-serrilhado:", - "GraphicsScalingFilterLabel": "Filtro de escala:", - "GraphicsScalingFilterTooltip": "Habilita escala do Framebuffer", - "GraphicsScalingFilterLevelLabel": "Nível", - "GraphicsScalingFilterLevelTooltip": "Define o nível do filtro de escala", - "SmaaLow": "SMAA Baixo", - "SmaaMedium": "SMAA Médio", - "SmaaHigh": "SMAA Alto", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Editar usuário", - "UserEditorTitleCreate": "Criar usuário", - "SettingsTabNetworkInterface": "Interface de rede:", - "NetworkInterfaceTooltip": "A interface de rede usada para recursos LAN (rede local)", - "NetworkInterfaceDefault": "Padrão", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ru_RU.json b/src/Ryujinx.Ava/Assets/Locales/ru_RU.json deleted file mode 100644 index a2128e52..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/ru_RU.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Русский", - "MenuBarFileOpenApplet": "Открыть апплет", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", - "SettingsTabInputDirectMouseAccess": "Прямой доступ к мыши", - "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", - "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", - "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", - "SettingsTabSystemUseHypervisor": "Использовать Гипервизор", - "MenuBarFile": "_Файл", - "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", - "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", - "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", - "MenuBarFileOpenLogsFolder": "Открыть папку журналов", - "MenuBarFileExit": "_Выход", - "MenuBarOptions": "Опции", - "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", - "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", - "MenuBarOptionsStopEmulation": "Остановить эмуляцию", - "MenuBarOptionsSettings": "_Параметры", - "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", - "MenuBarActions": "_Действия", - "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", - "MenuBarActionsScanAmiibo": "Сканировать Amiibo", - "MenuBarTools": "_Инструменты", - "MenuBarToolsInstallFirmware": "Установить прошивку", - "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", - "MenuBarToolsManageFileTypes": "Управление типами файлов", - "MenuBarToolsInstallFileTypes": "Установить типы файлов", - "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", - "MenuBarHelp": "Помощь", - "MenuBarHelpCheckForUpdates": "Проверка обновления", - "MenuBarHelpAbout": "О программе", - "MenuSearch": "Поиск...", - "GameListHeaderFavorite": "Избранные", - "GameListHeaderIcon": "Значок", - "GameListHeaderApplication": "Название", - "GameListHeaderDeveloper": "Разработчик", - "GameListHeaderVersion": "Версия", - "GameListHeaderTimePlayed": "Время воспроизведения", - "GameListHeaderLastPlayed": "Последняя игра", - "GameListHeaderFileExtension": "Расширение файла", - "GameListHeaderFileSize": "Размер файла", - "GameListHeaderPath": "Путь", - "GameListContextMenuOpenUserSaveDirectory": "Открыть папку сохранений пользователя", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", - "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", - "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", - "GameListContextMenuManageTitleUpdates": "Управление обновлениями названий", - "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", - "GameListContextMenuManageDlc": "Управление DLC", - "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", - "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", - "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды приложений и игр", - "GameListContextMenuCacheManagement": "Управление кэшем", - "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", - "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку кэша шейдеров", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", - "GameListContextMenuExtractData": "Извлечь данные", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Извлечение раздела ExeFS из текущих настроек приложения (включая обновления)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", - "GameListContextMenuExtractDataLogo": "Логотип", - "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", - "StatusBarGamesLoaded": "{0}/{1} Игр загружено", - "StatusBarSystemVersion": "Версия системы: {0}", - "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", - "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", - "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", - "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", - "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", - "Settings": "Параметры", - "SettingsTabGeneral": "Пользовательский интерфейс", - "SettingsTabGeneralGeneral": "Общее", - "SettingsTabGeneralEnableDiscordRichPresence": "Включить Discord Rich Presence", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", - "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", - "SettingsTabGeneralHideCursor": "Скрыть курсор", - "SettingsTabGeneralHideCursorNever": "Никогда", - "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", - "SettingsTabGeneralHideCursorAlways": "Всегда", - "SettingsTabGeneralGameDirectories": "Папки с играми", - "SettingsTabGeneralAdd": "Добавить", - "SettingsTabGeneralRemove": "Удалить", - "SettingsTabSystem": "Система", - "SettingsTabSystemCore": "Основные настройки", - "SettingsTabSystemSystemRegion": "Регион Системы:", - "SettingsTabSystemSystemRegionJapan": "Япония", - "SettingsTabSystemSystemRegionUSA": "США", - "SettingsTabSystemSystemRegionEurope": "Европа", - "SettingsTabSystemSystemRegionAustralia": "Австралия", - "SettingsTabSystemSystemRegionChina": "Китай", - "SettingsTabSystemSystemRegionKorea": "Корея", - "SettingsTabSystemSystemRegionTaiwan": "Тайвань", - "SettingsTabSystemSystemLanguage": "Язык системы:", - "SettingsTabSystemSystemLanguageJapanese": "Японский", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", - "SettingsTabSystemSystemLanguageFrench": "Французский", - "SettingsTabSystemSystemLanguageGerman": "Германский", - "SettingsTabSystemSystemLanguageItalian": "Итальянский", - "SettingsTabSystemSystemLanguageSpanish": "Испанский", - "SettingsTabSystemSystemLanguageChinese": "Китайский", - "SettingsTabSystemSystemLanguageKorean": "Корейский", - "SettingsTabSystemSystemLanguageDutch": "Нидерландский", - "SettingsTabSystemSystemLanguagePortuguese": "Португальский", - "SettingsTabSystemSystemLanguageRussian": "Русский", - "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", - "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", - "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", - "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", - "SettingsTabSystemSystemTime": "Время системы:", - "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", - "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", - "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", - "SettingsTabSystemAudioBackendDummy": "Муляж", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Хаки", - "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", - "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", - "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", - "SettingsTabGraphics": "Графика", - "SettingsTabGraphicsAPI": "Графические API", - "SettingsTabGraphicsEnableShaderCache": "Включить кэш шейдеров", - "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Масштаб:", - "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", - "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", - "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", - "SettingsTabGraphicsShaderDumpPath": "Путь дампа графического шейдера:", - "SettingsTabLogging": "Журналирование", - "SettingsTabLoggingLogging": "Журналирование", - "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", - "SettingsTabLoggingEnableStubLogs": "Включить журнал-заглушку", - "SettingsTabLoggingEnableInfoLogs": "Включить информационный журнал", - "SettingsTabLoggingEnableWarningLogs": "Включить журнал предупреждений", - "SettingsTabLoggingEnableErrorLogs": "Включить журнал ошибок", - "SettingsTabLoggingEnableTraceLogs": "Включить журнал трассировки", - "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", - "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", - "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", - "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", - "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: Снижает производительность", - "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", - "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", - "SettingsTabInput": "Управление", - "SettingsTabInputEnableDockedMode": "Включить режим закрепления", - "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", - "SettingsButtonSave": "Сохранить", - "SettingsButtonClose": "Закрыть", - "SettingsButtonOk": "Ок", - "SettingsButtonCancel": "Отмена", - "SettingsButtonApply": "Применить", - "ControllerSettingsPlayer": "Игрок", - "ControllerSettingsPlayer1": "Игрок 1", - "ControllerSettingsPlayer2": "Игрок 2", - "ControllerSettingsPlayer3": "Игрок 3", - "ControllerSettingsPlayer4": "Игрок 4", - "ControllerSettingsPlayer5": "Игрок 5", - "ControllerSettingsPlayer6": "Игрок 6", - "ControllerSettingsPlayer7": "Игрок 7", - "ControllerSettingsPlayer8": "Игрок 8", - "ControllerSettingsHandheld": "Портативный", - "ControllerSettingsInputDevice": "Устройство ввода", - "ControllerSettingsRefresh": "Обновить", - "ControllerSettingsDeviceDisabled": "Отключить", - "ControllerSettingsControllerType": "Тип контроллера", - "ControllerSettingsControllerTypeHandheld": "Портативный", - "ControllerSettingsControllerTypeProController": "Pro Контроллер", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", - "ControllerSettingsProfile": "Профиль", - "ControllerSettingsProfileDefault": "По умолчанию", - "ControllerSettingsLoad": "Загрузить", - "ControllerSettingsAdd": "Добавить", - "ControllerSettingsRemove": "Удалить", - "ControllerSettingsButtons": "Кнопки", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Направляющая панель", - "ControllerSettingsDPadUp": "Вверх", - "ControllerSettingsDPadDown": "Вниз", - "ControllerSettingsDPadLeft": "Влево", - "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Кнопка", - "ControllerSettingsStickUp": "Вверх", - "ControllerSettingsStickDown": "Вниз", - "ControllerSettingsStickLeft": "Влево", - "ControllerSettingsStickRight": "Вправо", - "ControllerSettingsStickStick": "Стик", - "ControllerSettingsStickInvertXAxis": "Инвертировать X ось", - "ControllerSettingsStickInvertYAxis": "Инвертировать Y ось", - "ControllerSettingsStickDeadzone": "Мёртвая зона:", - "ControllerSettingsLStick": "Левый стик", - "ControllerSettingsRStick": "Правый стик", - "ControllerSettingsTriggersLeft": "Триггеры слева", - "ControllerSettingsTriggersRight": "Триггеры справа", - "ControllerSettingsTriggersButtonsLeft": "Триггерные кнопки слева", - "ControllerSettingsTriggersButtonsRight": "Триггерные кнопки справа", - "ControllerSettingsTriggers": "Триггеры", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Левые кнопки", - "ControllerSettingsExtraButtonsRight": "Правые кнопки", - "ControllerSettingsMisc": "Разное", - "ControllerSettingsTriggerThreshold": "Порог срабатывания:", - "ControllerSettingsMotion": "Движение", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", - "ControllerSettingsMotionControllerSlot": "Слот контроллера:", - "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", - "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", - "ControllerSettingsMotionServerHost": "Хост сервера:", - "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", - "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", - "ControllerSettingsSave": "Сохранить", - "ControllerSettingsClose": "Закрыть", - "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", - "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", - "UserProfilesChangeProfileImage": "Изменить изображение профиля", - "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", - "UserProfilesAddNewProfile": "Добавить новый профиль", - "UserProfilesDelete": "Удалить", - "UserProfilesClose": "Закрыть", - "ProfileNameSelectionWatermark": "Выберите псевдоним", - "ProfileImageSelectionTitle": "Выбор изображения профиля", - "ProfileImageSelectionHeader": "Выберите изображение профиля", - "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", - "ProfileImageSelectionImportImage": "Импорт файла изображения", - "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", - "InputDialogTitle": "Диалоговое окно ввода", - "InputDialogOk": "ОК", - "InputDialogCancel": "Отмена", - "InputDialogAddNewProfileTitle": "Выберите имя профиля", - "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", - "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", - "AvatarChoose": "Выбор аватара", - "AvatarSetBackgroundColor": "Установить цвет фона", - "AvatarClose": "Закрыть", - "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", - "ControllerSettingsAddProfileToolTip": "Добавить профил", - "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", - "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", - "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", - "MenuBarFileToolsHideUi": "Скрыть UI", - "GameListContextMenuRunApplication": "Запуск приложения", - "GameListContextMenuToggleFavorite": "Переключить Избранное", - "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", - "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", - "SettingsTabGeneralThemeBaseStyleLight": "Светлая", - "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", - "ButtonBrowse": "Обзор", - "ControllerSettingsConfigureGeneral": "Настройка", - "ControllerSettingsRumble": "Вибрация", - "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", - "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", - "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", - "DialogConfirmationTitle": "Ryujinx - Подтверждение", - "DialogUpdaterTitle": "Ryujinx - Обновление", - "DialogErrorTitle": "Ryujinx - Ошибка", - "DialogWarningTitle": "Ryujinx - Предупреждение", - "DialogExitTitle": "Ryujinx - Выход", - "DialogErrorMessage": "Ryujinx обнаружил ошибку", - "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", - "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", - "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", - "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", - "FolderDialogExtractTitle": "Выберите папку для извлечения", - "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", - "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", - "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", - "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", - "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", - "DialogUpdaterDownloadingMessage": "Загрузка обновления...", - "DialogUpdaterExtractionMessage": "Извлечение обновления...", - "DialogUpdaterRenamingMessage": "Переименование обновления...", - "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", - "DialogUpdaterCompleteMessage": "Обновление завершено!", - "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", - "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", - "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только x64 системы)", - "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", - "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", - "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", - "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", - "DialogRestartRequiredMessage": "Требуется перезагрузка", - "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", - "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", - "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", - "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", - "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", - "DialogInstallFileTypesSuccessMessage": "Успешно установлены типы файлов!", - "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", - "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", - "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", - "DialogOpenSettingsWindowLabel": "Открыть окно настроек", - "DialogControllerAppletTitle": "Апплет контроллера", - "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", - "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", - "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", - "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", - "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", - "DialogProfileDeleteProfileTitle": "Удаление профиля", - "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", - "DialogWarning": "Внимание", - "DialogPPTCDeletionMessage": "Вы собираетесь удалить кэш PPTC для:\n\n{0}\n\nВы уверены, что хотите продолжить?", - "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", - "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", - "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", - "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", - "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", - "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", - "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", - "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", - "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", - "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", - "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", - "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", - "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", - "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", - "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", - "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", - "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", - "CommonAuto": "Автоматически", - "CommonOff": "Выключен", - "CommonOn": "Включен", - "InputDialogYes": "Да", - "InputDialogNo": "Нет", - "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", - "MenuBarOptionsPauseEmulation": "Пауза", - "MenuBarOptionsResumeEmulation": "Продолжить", - "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", - "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", - "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", - "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", - "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", - "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", - "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", - "AboutRyujinxAboutTitle": "О программе:", - "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", - "AboutRyujinxMaintainersTitle": "Поддерживается:", - "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", - "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", - "AmiiboSeriesLabel": "Серия Amiibo", - "AmiiboCharacterLabel": "Персонаж", - "AmiiboScanButtonLabel": "Сканировать", - "AmiiboOptionsShowAllLabel": "Показать все Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", - "DlcManagerTableHeadingEnabledLabel": "Велючено", - "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", - "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", - "DlcManagerTableHeadingFullPathLabel": "Полный путь", - "DlcManagerRemoveAllButton": "Убрать все", - "DlcManagerEnableAllButton": "Включить все", - "DlcManagerDisableAllButton": "Отключить все", - "MenuBarOptionsChangeLanguage": "Изменить язык", - "MenuBarShowFileTypes": "Показать типы файлов", - "CommonSort": "Сортировать", - "CommonShowNames": "Показать названия", - "CommonFavorite": "Избранные", - "OrderAscending": "По возрастанию", - "OrderDescending": "По убыванию", - "SettingsTabGraphicsFeatures": "Функции", - "ErrorWindowTitle": "Окно ошибки", - "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", - "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", - "AddGameDirTooltip": "AДобавить папку с игрой в список", - "RemoveGameDirTooltip": "Удалить выбранную папку игры", - "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", - "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", - "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", - "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", - "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", - "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", - "RegionTooltip": "Изменение региона системы", - "LanguageTooltip": "Изменение языка системы", - "TimezoneTooltip": "Изменение часового пояса системы", - "TimeTooltip": "Изменение системного времени", - "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш. Если планируете отключить вериткальную синхронизацию, мы рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", - "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", - "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для содействия в устранении неполадок.\n\nРекомендуется оставить включенным.", - "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", - "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", - "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", - "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", - "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстрее, но менее безопасно. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", - "UseHypervisorTooltip": "Использует Гипервизор вместо JIT. Значительно увеличивает производительность, но может быть работать нестабильно.", - "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch для разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", - "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Это поможет избежать вылеты при загрузке определенных игр.\n\nРекомендуется оставить выключенным.", - "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "GalThreadingTooltip": "Выполняет команды графического бэкенда на во втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Масштабирование разрешения", - "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение в игре по умолчанию)", - "AspectRatioTooltip": "Соотношение сторон, применяемое к окну.", - "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", - "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", - "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", - "InfoLogTooltip": "Включает печать сообщений информационного журнала. Не влияет на производительность.", - "WarnLogTooltip": "Включает печать сообщений журнала предупреждений. Не влияет на производительность.", - "ErrorLogTooltip": "Включает печать сообщений журнала ошибок. Не влияет на производительность.", - "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", - "GuestLogTooltip": "Включает печать сообщений гостевого журнала. Не влияет на производительность.", - "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", - "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", - "DeveloperOptionTooltip": "Используйте с осторожностью", - "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", - "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", - "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", - "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", - "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", - "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", - "ExitTooltip": "Выйти из Ryujinx", - "OpenSettingsTooltip": "Открыть окно настроек", - "OpenProfileManagerTooltip": "Открыть диспетчер профилей", - "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", - "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", - "OpenAboutTooltip": "Открыть окно «О программе»", - "GridSize": "Размер сетки", - "GridSizeTooltip": "Изменение размера элементов сетки", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", - "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", - "SettingsTabSystemAudioVolume": "Громкость: ", - "AudioVolumeTooltip": "Изменяет громкость звука", - "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет/сетевой режим", - "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", - "GameListContextMenuManageCheatToolTip": "Управление читами", - "GameListContextMenuManageCheat": "Управление читами", - "ControllerSettingsStickRange": "Диапазон:", - "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", - "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", - "SettingsTabCpu": "ЦП", - "SettingsTabAudio": "Аудио", - "SettingsTabNetwork": "Сеть", - "SettingsTabNetworkConnection": "Подключение к сети", - "SettingsTabCpuCache": "Кэш ЦП", - "SettingsTabCpuMemory": "Память ЦП", - "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", - "UpdaterDisabledWarningTitle": "Обновление выключено!", - "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку SD-карты Atmosphere, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", - "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", - "IconSize": "Размер иконки", - "IconSizeTooltip": "Изменить размер игровых иконок", - "MenuBarOptionsShowConsole": "Показать консоль", - "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", - "UserErrorNoKeys": "Ключи не найдены", - "UserErrorNoFirmware": "Прошивка не найдена", - "UserErrorFirmwareParsingFailed": "Ошибка извлечения прошивки", - "UserErrorApplicationNotFound": "Приложение не найдено", - "UserErrorUnknown": "Неизвестная ошибка", - "UserErrorUndefined": "Неопределенная ошибка", - "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", - "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", - "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти действительное приложение по указанному пути.", - "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", - "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", - "OpenSetupGuideMessage": "Открыть руководство по установке", - "NoUpdate": "Нет обновлений", - "TitleUpdateVersionLabel": "Version {0} - {1}", - "RyujinxInfo": "Ryujinx - Информация", - "RyujinxConfirm": "Ryujinx - Подтверждение", - "FileDialogAllTypes": "Все типы", - "Never": "Никогда", - "SwkbdMinCharacters": "Должно быть не менее {0} символов.", - "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", - "SoftwareKeyboard": "Программная клавиатура", - "SoftwareKeyboardModeNumbersOnly": "Должны быть только цифры", - "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", - "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", - "DialogControllerAppletMessagePlayerRange": "Приложение запрашивает {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте сейчас или нажмите \"Закрыть\".", - "DialogControllerAppletMessage": "Приложение запрашивает ровно {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте Ввод или нажмите \"Закрыть\".", - "DialogControllerAppletDockModeSet": "Установлен стационарный режим. Портативный режим недоступен.", - "UpdaterRenaming": "Переименование старых файлов...", - "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", - "UpdaterAddingFiles": "Добавление новых файлов...", - "UpdaterExtracting": "Извлечение обновления...", - "UpdaterDownloading": "Загрузка обновления...", - "Game": "Игра", - "Docked": "Стационарный режим", - "Handheld": "Портативный режим", - "ConnectionError": "Ошибка соединения!", - "AboutPageDeveloperListMore": "{0} и больше...", - "ApiError": "Ошибка API.", - "LoadingHeading": "Загрузка {0}", - "CompilingPPTC": "Компиляция PTC", - "CompilingShaders": "Компиляция шейдеров", - "AllKeyboards": "Все клавиатуры", - "OpenFileDialogTitle": "Выберите совместимый файл для открытия", - "OpenFolderDialogTitle": "Выберите папку с распакованной игрой", - "AllSupportedFormats": "Все поддерживаемые форматы", - "RyujinxUpdater": "Ryujinx - Обновление", - "SettingsTabHotkeys": "Горячие клавиши", - "SettingsTabHotkeysHotkeys": "Горячие клавиши", - "SettingsTabHotkeysToggleVsyncHotkey": "Переключить VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", - "SettingsTabHotkeysShowUiHotkey": "Показать UI:", - "SettingsTabHotkeysPauseHotkey": "Пауза:", - "SettingsTabHotkeysToggleMuteHotkey": "Приглушить:", - "ControllerMotionTitle": "Настройки управления движением", - "ControllerRumbleTitle": "Настройки вибрации", - "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", - "SettingsXamlThemeFile": "Файл темы Xaml", - "AvatarWindowTitle": "Управление аккаунтами - Аватар", - "Amiibo": "Amiibo", - "Unknown": "Неизвестно", - "Usage": "Применение", - "Writable": "Доступно для записи", - "SelectDlcDialogTitle": "Выберите файлы DLC", - "SelectUpdateDialogTitle": "Выберите файлы обновления", - "UserProfileWindowTitle": "Менеджер профилей пользователей", - "CheatWindowTitle": "Менеджер читов", - "DlcWindowTitle": "Управление загружаемым контентом для {0} ({1})", - "UpdateWindowTitle": "Менеджер обновления названий", - "CheatWindowHeading": "Читы доступны для {0} [{1}]", - "BuildId": "ID версии:", - "DlcWindowHeading": "{0} Загружаемый контент", - "UserProfilesEditProfile": "Изменить выбранные", - "Cancel": "Отмена", - "Save": "Сохранить", - "Discard": "Отменить", - "UserProfilesSetProfileImage": "Установить изображение профиля", - "UserProfileEmptyNameError": "Имя обязательно", - "UserProfileNoImageError": "Изображение профиля должно быть установлено", - "GameUpdateWindowHeading": "Обновление доступно для {0} ({1})", - "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", - "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", - "UserProfilesName": "Имя:", - "UserProfilesUserId": "ID пользователя:", - "SettingsTabGraphicsBackend": "Бэкенд графики", - "SettingsTabGraphicsBackendTooltip": "Использовать графический бэкенд", - "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для GPU с 4 гб видеопамяти и менее.\n\nРекомендуется оставить выключенным.", - "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите видеокарту, которая будет использоваться с графическим бэкендом Vulkan.\n\nНе влияет на GPU, который будет использовать OpenGL.\n\nУстановите графический процессор, помеченный как \"dGPU\", если вы не уверены. Если его нет, оставьте нетронутым.", - "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", - "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", - "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", - "RyujinxUpdaterMessage": "Вы хотите обновить Ryujinx до последней версии?", - "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", - "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", - "SettingsEnableMacroHLE": "Включить Macro HLE", - "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", - "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", - "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкэнд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", - "VolumeShort": "Громкость", - "UserProfilesManageSaves": "Управление сохранениями", - "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", - "IrreversibleActionNote": "Данное действие является необратимым.", - "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", - "SaveManagerTitle": "Менеджер сохранений", - "Name": "Название", - "Size": "Размер", - "Search": "Поиск", - "UserProfilesRecoverLostAccounts": "Восстановить утерянные аккаунты", - "Recover": "Восстановление", - "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", - "UserProfilesRecoverEmptyList": "Нет профилей для восстановления", - "GraphicsAATooltip": "Применяет сглаживание к рейдеру игры.", - "GraphicsAALabel": "Сглаживание:", - "GraphicsScalingFilterLabel": "Масштабирующий фильтр:", - "GraphicsScalingFilterTooltip": "Включает масштабирование кадрового буфера", - "GraphicsScalingFilterLevelLabel": "Уровень", - "GraphicsScalingFilterLevelTooltip": "Установить уровень фильтра масштабирования", - "SmaaLow": "SMAA Низкое", - "SmaaMedium": "SMAA Среднее", - "SmaaHigh": "SMAA Высокое", - "SmaaUltra": "SMAA Ультра", - "UserEditorTitle": "Редактирование пользователя", - "UserEditorTitleCreate": "Создание пользователя", - "SettingsTabNetworkInterface": "Сетевой Интерфейс", - "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN", - "NetworkInterfaceDefault": "По умолчанию", - "PackagingShaders": "Упаковка шейдеров", - "AboutChangelogButton": "Список изменений на GitHub", - "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии в браузере." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/tr_TR.json b/src/Ryujinx.Ava/Assets/Locales/tr_TR.json deleted file mode 100644 index 3f70a781..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/tr_TR.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Türkçe", - "MenuBarFileOpenApplet": "Applet'i Aç", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", - "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", - "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", - "SettingsTabSystemMemoryManagerModeSoftware": "Yazılım", - "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", - "SettingsTabSystemUseHypervisor": "Hypervisor Kullan", - "MenuBarFile": "_Dosya", - "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", - "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", - "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", - "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", - "MenuBarFileExit": "_Çıkış", - "MenuBarOptions": "Seçenekler", - "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", - "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", - "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", - "MenuBarOptionsSettings": "_Seçenekler", - "MenuBarOptionsManageUserProfiles": "_Kullanıcı Profillerini Yönet", - "MenuBarActions": "_Eylemler", - "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", - "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", - "MenuBarTools": "_Araçlar", - "MenuBarToolsInstallFirmware": "Yazılım Yükle", - "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Bir Dizin Üzerinden Yazılım Yükle", - "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", - "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", - "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", - "MenuBarHelp": "Yardım", - "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", - "MenuBarHelpAbout": "Hakkında", - "MenuSearch": "Ara...", - "GameListHeaderFavorite": "Favori", - "GameListHeaderIcon": "Simge", - "GameListHeaderApplication": "Oyun Adı", - "GameListHeaderDeveloper": "Geliştirici", - "GameListHeaderVersion": "Sürüm", - "GameListHeaderTimePlayed": "Oynama Süresi", - "GameListHeaderLastPlayed": "Son Oynama Tarihi", - "GameListHeaderFileExtension": "Dosya Uzantısı", - "GameListHeaderFileSize": "Dosya Boyutu", - "GameListHeaderPath": "Yol", - "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar", - "GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar", - "GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar", - "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", - "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", - "GameListContextMenuManageDlc": "DLC'leri Yönet", - "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", - "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", - "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", - "GameListContextMenuCacheManagement": "Önbellek Yönetimi", - "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", - "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Önbelleğini Temizle", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader önbelleğini temizler", - "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Önbelleğinin bulunduğu dizini açar", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Önbelleği Dizinini Aç", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader önbelleğinin bulunduğu dizini açar", - "GameListContextMenuExtractData": "Veriyi Ayıkla", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", - "GameListContextMenuExtractDataLogo": "Simge", - "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", - "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", - "StatusBarSystemVersion": "Sistem Sürümü: {0}", - "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", - "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count değerini {0} sayısına yükseltmek ister misiniz", - "LinuxVmMaxMapCountDialogTextSecondary": "Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Evet, bir sonraki yeniden başlatmaya kadar", - "LinuxVmMaxMapCountDialogButtonPersistent": "Evet, kalıcı olarak", - "LinuxVmMaxMapCountWarningTextPrimary": "İzin verilen maksimum bellek haritası değeri tavsiye edildiğinden daha düşük. ", - "LinuxVmMaxMapCountWarningTextSecondary": "Şu anki vm.max_map_count değeri {0}, bu {1} değerinden daha az. Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.\n\nManuel olarak bu limiti arttırmayı deneyebilir ya da pkexec'i yükleyebilirsiniz, bu da Ryujinx'in yardımcı olmasına izin verir.", - "Settings": "Ayarlar", - "SettingsTabGeneral": "Kullancı Arayüzü", - "SettingsTabGeneralGeneral": "Genel", - "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", - "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", - "SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:", - "SettingsTabGeneralHideCursorNever": "Hiçbir Zaman", - "SettingsTabGeneralHideCursorOnIdle": "Hareketsiz Durumda", - "SettingsTabGeneralHideCursorAlways": "Her Zaman", - "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", - "SettingsTabGeneralAdd": "Ekle", - "SettingsTabGeneralRemove": "Kaldır", - "SettingsTabSystem": "Sistem", - "SettingsTabSystemCore": "Çekirdek", - "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", - "SettingsTabSystemSystemRegionJapan": "Japonya", - "SettingsTabSystemSystemRegionUSA": "ABD", - "SettingsTabSystemSystemRegionEurope": "Avrupa", - "SettingsTabSystemSystemRegionAustralia": "Avustralya", - "SettingsTabSystemSystemRegionChina": "Çin", - "SettingsTabSystemSystemRegionKorea": "Kore", - "SettingsTabSystemSystemRegionTaiwan": "Tayvan", - "SettingsTabSystemSystemLanguage": "Sistem Dili:", - "SettingsTabSystemSystemLanguageJapanese": "Japonca", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", - "SettingsTabSystemSystemLanguageFrench": "Fransızca", - "SettingsTabSystemSystemLanguageGerman": "Almanca", - "SettingsTabSystemSystemLanguageItalian": "İtalyanca", - "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", - "SettingsTabSystemSystemLanguageChinese": "Çince", - "SettingsTabSystemSystemLanguageKorean": "Korece", - "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", - "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", - "SettingsTabSystemSystemLanguageRussian": "Rusça", - "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", - "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", - "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", - "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", - "SettingsTabSystemSystemTime": "Sistem Saati:", - "SettingsTabSystemEnableVsync": "Dikey Eşitleme", - "SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)", - "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri", - "SettingsTabSystemAudioBackend": "Ses Motoru:", - "SettingsTabSystemAudioBackendDummy": "Yapay", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Hack'ler", - "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", - "SettingsTabSystemExpandDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", - "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", - "SettingsTabGraphics": "Grafikler", - "SettingsTabGraphicsAPI": "Grafikler API", - "SettingsTabGraphicsEnableShaderCache": "Shader Önbelleğini Etkinleştir", - "SettingsTabGraphicsAnisotropicFiltering": "Eşyönsüz Doku Süzmesi:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Çözünürlük Ölçeği:", - "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", - "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak İçin Genişlet", - "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", - "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", - "SettingsTabLogging": "Loglama", - "SettingsTabLoggingLogging": "Loglama", - "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", - "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", - "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", - "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", - "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", - "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", - "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", - "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", - "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", - "SettingsTabLoggingDeveloperOptionsNote": "UYARI: Oyun performansı azalacak", - "SettingsTabLoggingGraphicsBackendLogLevel": "Grafik Arka Uç Günlük Düzeyi", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiçbiri", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Hata", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Hepsi", - "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", - "SettingsTabInput": "Giriş Yöntemi", - "SettingsTabInputEnableDockedMode": "Docked Modu Etkinleştir", - "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", - "SettingsButtonSave": "Kaydet", - "SettingsButtonClose": "Kapat", - "SettingsButtonOk": "Tamam", - "SettingsButtonCancel": "İptal", - "SettingsButtonApply": "Uygula", - "ControllerSettingsPlayer": "Oyuncu", - "ControllerSettingsPlayer1": "Oyuncu 1", - "ControllerSettingsPlayer2": "Oyuncu 2", - "ControllerSettingsPlayer3": "Oyuncu 3", - "ControllerSettingsPlayer4": "Oyuncu 4", - "ControllerSettingsPlayer5": "Oyuncu 5", - "ControllerSettingsPlayer6": "Oyuncu 6", - "ControllerSettingsPlayer7": "Oyuncu 7", - "ControllerSettingsPlayer8": "Oyuncu 8", - "ControllerSettingsHandheld": "Portatif Mod", - "ControllerSettingsInputDevice": "Giriş Cihazı", - "ControllerSettingsRefresh": "Yenile", - "ControllerSettingsDeviceDisabled": "Devre Dışı", - "ControllerSettingsControllerType": "Kontrolcü Tipi", - "ControllerSettingsControllerTypeHandheld": "Portatif Mod", - "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", - "ControllerSettingsProfile": "Profil", - "ControllerSettingsProfileDefault": "Varsayılan", - "ControllerSettingsLoad": "Yükle", - "ControllerSettingsAdd": "Ekle", - "ControllerSettingsRemove": "Kaldır", - "ControllerSettingsButtons": "Tuşlar", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Yön Tuşları", - "ControllerSettingsDPadUp": "Yukarı", - "ControllerSettingsDPadDown": "Aşağı", - "ControllerSettingsDPadLeft": "Sol", - "ControllerSettingsDPadRight": "Sağ", - "ControllerSettingsStickButton": "Tuş", - "ControllerSettingsStickUp": "Yukarı", - "ControllerSettingsStickDown": "Aşağı", - "ControllerSettingsStickLeft": "Sol", - "ControllerSettingsStickRight": "Sağ", - "ControllerSettingsStickStick": "Analog", - "ControllerSettingsStickInvertXAxis": "X Eksenini Tersine Çevir", - "ControllerSettingsStickInvertYAxis": "Y Eksenini Tersine Çevir", - "ControllerSettingsStickDeadzone": "Ölü Bölge", - "ControllerSettingsLStick": "Sol Analog", - "ControllerSettingsRStick": "Sağ Analog", - "ControllerSettingsTriggersLeft": "Tetikler Sol", - "ControllerSettingsTriggersRight": "Tetikler Sağ", - "ControllerSettingsTriggersButtonsLeft": "Tetik Tuşları Sol", - "ControllerSettingsTriggersButtonsRight": "Tetik Tuşları Sağ", - "ControllerSettingsTriggers": "Tetikler", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Tuşlar Sol", - "ControllerSettingsExtraButtonsRight": "Tuşlar Sağ", - "ControllerSettingsMisc": "Diğer", - "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", - "ControllerSettingsMotion": "Hareket", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", - "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", - "ControllerSettingsMotionMirrorInput": "Girişi Aynala", - "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", - "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", - "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", - "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", - "ControllerSettingsSave": "Kaydet", - "ControllerSettingsClose": "Kapat", - "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", - "UserProfilesSaveProfileName": "Profil İsmini Kaydet", - "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", - "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", - "UserProfilesAddNewProfile": "Yeni Profil Ekle", - "UserProfilesDelete": "Sil", - "UserProfilesClose": "Kapat", - "ProfileNameSelectionWatermark": "Kullanıcı Adı Seç", - "ProfileImageSelectionTitle": "Profil Resmi Seçimi", - "ProfileImageSelectionHeader": "Profil Resmi Seç", - "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", - "ProfileImageSelectionImportImage": "Resim İçeri Aktar", - "ProfileImageSelectionSelectAvatar": "Yazılım Avatarı Seç", - "InputDialogTitle": "Giriş Yöntemi Diyaloğu", - "InputDialogOk": "Tamam", - "InputDialogCancel": "İptal", - "InputDialogAddNewProfileTitle": "Profil İsmini Seç", - "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", - "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", - "AvatarChoose": "Seç", - "AvatarSetBackgroundColor": "Arka Plan Rengi Ayarla", - "AvatarClose": "Kapat", - "ControllerSettingsLoadProfileToolTip": "Profil Yükle", - "ControllerSettingsAddProfileToolTip": "Profil Ekle", - "ControllerSettingsRemoveProfileToolTip": "Profili Kaldır", - "ControllerSettingsSaveProfileToolTip": "Profili Kaydet", - "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", - "MenuBarFileToolsHideUi": "Arayüzü Gizle", - "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", - "GameListContextMenuToggleFavorite": "Favori Ayarla", - "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", - "SettingsTabGeneralThemeBaseStyle": "Temel Stil", - "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", - "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", - "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", - "ButtonBrowse": "Göz At", - "ControllerSettingsConfigureGeneral": "Ayarla", - "ControllerSettingsRumble": "Titreşim", - "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", - "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Seviyesi", - "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi bulunamadı", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", - "DialogConfirmationTitle": "Ryujinx - Onay", - "DialogUpdaterTitle": "Ryujinx - Güncelleyici", - "DialogErrorTitle": "Ryujinx - Hata", - "DialogWarningTitle": "Ryujinx - Uyarı", - "DialogExitTitle": "Ryujinx - Çıkış", - "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", - "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", - "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", - "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken bir hata oluştu: {0}", - "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", - "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", - "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", - "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", - "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için kayıt dosyasını okuyun.", - "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", - "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü dönüştürülemedi.", - "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", - "DialogUpdaterFailedToGetVersionMessage": "GitHub tarafından sürüm bilgileri alınırken bir hata oluştu. Eğer yeni sürüm için hazırlıklar yapılıyorsa bu hatayı almanız olasıdır. Lütfen birkaç dakika sonra tekrar deneyiniz.", - "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü dönüştürülemedi.", - "DialogUpdaterDownloadingMessage": "Güncelleme İndiriliyor...", - "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", - "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", - "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", - "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", - "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", - "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", - "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", - "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", - "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", - "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", - "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm için lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", - "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", - "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", - "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", - "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", - "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", - "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", - "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", - "DialogInstallFileTypesErrorMessage": "Dosya uzantıları yükleme işlemi başarısız oldu.", - "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", - "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", - "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", - "DialogControllerAppletTitle": "Kontrolcü Applet'i", - "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", - "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", - "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", - "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", - "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", - "DialogProfileDeleteProfileTitle": "Profil Siliniyor", - "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", - "DialogWarning": "Uyarı", - "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", - "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", - "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", - "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", - "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", - "DialogInvalidTitleIdErrorMessage": "Arayüz hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", - "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", - "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", - "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", - "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", - "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", - "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", - "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", - "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", - "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", - "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", - "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", - "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", - "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", - "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", - "SettingsTabGraphicsFeaturesOptions": "Özellikler", - "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", - "CommonAuto": "Otomatik", - "CommonOff": "Kapalı", - "CommonOn": "Açık", - "InputDialogYes": "Evet", - "InputDialogNo": "Hayır", - "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçersiz karakter içeriyor. Lütfen tekrar deneyin.", - "MenuBarOptionsPauseEmulation": "Durdur", - "MenuBarOptionsResumeEmulation": "Devam Et", - "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", - "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", - "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", - "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", - "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", - "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", - "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", - "AboutRyujinxAboutTitle": "Hakkında:", - "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", - "AboutRyujinxMaintainersTitle": "Geliştiriciler:", - "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", - "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", - "AmiiboSeriesLabel": "Amiibo Serisi", - "AmiiboCharacterLabel": "Karakter", - "AmiiboScanButtonLabel": "Tarat", - "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", - "AmiiboOptionsUsRandomTagLabel": "Hack: Rastgele bir Uuid kullan", - "DlcManagerTableHeadingEnabledLabel": "Etkin", - "DlcManagerTableHeadingTitleIdLabel": "Başlık ID", - "DlcManagerTableHeadingContainerPathLabel": "Container Yol", - "DlcManagerTableHeadingFullPathLabel": "Tam Yol", - "DlcManagerRemoveAllButton": "Tümünü kaldır", - "DlcManagerEnableAllButton": "Tümünü Aktif Et", - "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", - "MenuBarOptionsChangeLanguage": "Dili Değiştir", - "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", - "CommonSort": "Sırala", - "CommonShowNames": "İsimleri Göster", - "CommonFavorite": "Favori", - "OrderAscending": "Artan", - "OrderDescending": "Azalan", - "SettingsTabGraphicsFeatures": "Özellikler & İyileştirmeler", - "ErrorWindowTitle": "Hata Penceresi", - "ToggleDiscordTooltip": "Ryujinx'i \"şimdi oynanıyor\" Discord aktivitesinde göstermeyi veya göstermemeyi seçin", - "AddGameDirBoxTooltip": "Listeye eklemek için oyun dizini seçin", - "AddGameDirTooltip": "Listeye oyun dizini ekle", - "RemoveGameDirTooltip": "Seçili oyun dizinini kaldır", - "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", - "CustomThemePathTooltip": "Özel arayüz temasının yolu", - "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", - "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", - "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", - "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", - "RegionTooltip": "Sistem Bölgesini Değiştir", - "LanguageTooltip": "Sistem Dilini Değiştir", - "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", - "TimeTooltip": "Sistem Saatini Değiştir", - "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", - "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", - "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", - "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", - "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", - "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", - "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", - "MemoryManagerUnsafeTooltip": "Hafızayı doğrudan tahsis eder, ancak host aralığına erişimden önce adresi maskelemez. Daha iyi performansa karşılık emniyetten ödün verir. Misafir uygulama Ryujinx içerisinden istediği hafızaya erişebilir, bu sebeple bu seçenek ile sadece güvendiğiniz uygulamaları çalıştırın.", - "UseHypervisorTooltip": "JIT yerine Hypervisor kullan. Uygun durumlarda performansı büyük oranda arttırır. Ancak şu anki halinde stabil durumda çalışmayabilir.", - "DRamTooltip": "Emüle edilen sistem hafızasını 4GiB'dan 6GiB'a yükseltir.\n\nBu seçenek yalnızca yüksek çözünürlük doku paketleri veya 4k çözünürlük modları için kullanılır. Performansı artırMAZ!\n\nEmin değilseniz devre dışı bırakın.", - "IgnoreMissingServicesTooltip": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.", - "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", - "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", - "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", - "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", - "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", - "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", - "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", - "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", - "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", - "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", - "InfoLogTooltip": "Bilgi log mesajlarını konsola yazdırır. Performansı etkilemez.", - "WarnLogTooltip": "Uyarı log mesajlarını konsola yazdırır. Performansı etkilemez.", - "ErrorLogTooltip": "Hata log mesajlarını konsola yazdırır. Performansı etkilemez.", - "TraceLogTooltip": "Trace log mesajlarını konsola yazdırır. Performansı etkilemez.", - "GuestLogTooltip": "Guest log mesajlarını konsola yazdırır. Performansı etkilemez.", - "FileAccessLogTooltip": "Dosya sistemi erişim log mesajlarını konsola yazdırır.", - "FSAccessLogModeTooltip": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür", - "DeveloperOptionTooltip": "Dikkatli kullanın", - "OpenGlLogLevel": "Uygun log seviyesinin aktif olmasını gerektirir", - "DebugLogTooltip": "Debug log mesajlarını konsola yazdırır.\n\nBu seçeneği yalnızca geliştirici üyemiz belirtirse aktifleştirin, çünkü bu seçenek log dosyasını okumayı zorlaştırır ve emülatörün performansını düşürür.", - "LoadApplicationFileTooltip": "Switch ile uyumlu bir dosya yüklemek için dosya tarayıcısını açar", - "LoadApplicationFolderTooltip": "Switch ile uyumlu ayrıştırılmamış bir uygulama yüklemek için dosya tarayıcısını açar", - "OpenRyujinxFolderTooltip": "Ryujinx dosya sistem klasörünü açar", - "OpenRyujinxLogsTooltip": "Log dosyalarının bulunduğu klasörü açar", - "ExitTooltip": "Ryujinx'ten çıkış yapmayı sağlar", - "OpenSettingsTooltip": "Seçenekler penceresini açar", - "OpenProfileManagerTooltip": "Kullanıcı profil yöneticisi penceresini açar", - "StopEmulationTooltip": "Oynanmakta olan oyunun emülasyonunu durdurup oyun seçimine geri döndürür", - "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetlemeyi sağlar", - "OpenAboutTooltip": "Hakkında penceresini açar", - "GridSize": "Öge Boyutu", - "GridSizeTooltip": "Grid ögelerinin boyutunu değiştirmeyi sağlar", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", - "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", - "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", - "AudioVolumeTooltip": "Ses seviyesini değiştirir", - "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimi/LAN Modu", - "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", - "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", - "GameListContextMenuManageCheat": "Hileleri Yönet", - "ControllerSettingsStickRange": "Menzil:", - "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", - "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", - "SettingsTabCpu": "İşlemci", - "SettingsTabAudio": "Ses", - "SettingsTabNetwork": "Ağ", - "SettingsTabNetworkConnection": "Ağ Bağlantısı", - "SettingsTabCpuCache": "İşlemci Belleği", - "SettingsTabCpuMemory": "CPU Hafızası", - "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", - "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", - "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", - "IconSize": "Ikon Boyutu", - "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", - "MenuBarOptionsShowConsole": "Konsol'u Göster", - "ShaderCachePurgeError": "Belirtilen shader cache temizlenirken hata {0}: {1}", - "UserErrorNoKeys": "Keys bulunamadı", - "UserErrorNoFirmware": "Firmware bulunamadı", - "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", - "UserErrorApplicationNotFound": "Uygulama bulunamadı", - "UserErrorUnknown": "Bilinmeyen hata", - "UserErrorUndefined": "Tanımlanmayan hata", - "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyasını bulamadı", - "UserErrorNoFirmwareDescription": "Ryujinx yüklü herhangi firmware bulamadı", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.", - "UserErrorApplicationNotFoundDescription": "Ryujinx belirtilen yolda geçerli bir uygulama bulamadı.", - "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", - "UserErrorUndefinedDescription": "Tanımlanmayan bir hata oluştu! Bu durum ile karşılaşılmamalıydı, lütfen bir geliştirici ile iletişime geçin!", - "OpenSetupGuideMessage": "Kurulum Kılavuzunu Aç", - "NoUpdate": "Güncelleme Yok", - "TitleUpdateVersionLabel": "Sürüm {0} - {1}", - "RyujinxInfo": "Ryujinx - Bilgi", - "RyujinxConfirm": "Ryujinx - Doğrulama", - "FileDialogAllTypes": "Tüm türler", - "Never": "Hiçbir Zaman", - "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", - "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", - "SoftwareKeyboard": "Yazılım Klavyesi", - "SoftwareKeyboardModeNumbersOnly": "Sadece Numara Olabilir", - "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", - "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", - "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", - "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", - "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", - "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", - "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", - "UpdaterDownloading": "Güncelleme İndiriliyor...", - "Game": "Oyun", - "Docked": "Yerleştirildi", - "Handheld": "El tipi", - "ConnectionError": "Bağlantı Hatası.", - "AboutPageDeveloperListMore": "{0} ve daha fazla...", - "ApiError": "API Hatası.", - "LoadingHeading": "{0} Yükleniyor", - "CompilingPPTC": "PTC Derleniyor", - "CompilingShaders": "Shaderlar Derleniyor", - "AllKeyboards": "Tüm Klavyeler", - "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", - "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", - "AllSupportedFormats": "Tüm Desteklenen Formatlar", - "RyujinxUpdater": "Ryujinx Güncelleyicisi", - "SettingsTabHotkeys": "Klavye Kısayolları", - "SettingsTabHotkeysHotkeys": "Klavye Kısayolları", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir/Devre Dışı Bırak:", - "SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:", - "SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:", - "SettingsTabHotkeysPauseHotkey": "Durdur:", - "SettingsTabHotkeysToggleMuteHotkey": "Sustur:", - "ControllerMotionTitle": "Hareket Kontrol Seçenekleri", - "ControllerRumbleTitle": "Titreşim Seçenekleri", - "SettingsSelectThemeFileDialogTitle": "Tema Dosyası Seç", - "SettingsXamlThemeFile": "Xaml Tema Dosyası", - "AvatarWindowTitle": "Hesapları Yönet - Avatar", - "Amiibo": "Amiibo", - "Unknown": "Bilinmeyen", - "Usage": "Kullanım", - "Writable": "Yazılabilir", - "SelectDlcDialogTitle": "DLC dosyalarını seç", - "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", - "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", - "CheatWindowTitle": "Oyun Hilelerini Yönet", - "DlcWindowTitle": "Oyun DLC'lerini Yönet", - "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", - "CheatWindowHeading": "{0} için Hile mevcut [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "{0} için DLC mevcut [{1}]", - "UserProfilesEditProfile": "Seçiliyi Düzenle", - "Cancel": "İptal", - "Save": "Kaydet", - "Discard": "Iskarta", - "UserProfilesSetProfileImage": "Profil Resmi Ayarla", - "UserProfileEmptyNameError": "İsim gerekli", - "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", - "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", - "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:", - "UserProfilesName": "İsim:", - "UserProfilesUserId": "Kullanıcı Adı:", - "SettingsTabGraphicsBackend": "Grafik Arka Ucu", - "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", - "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", - "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", - "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", - "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", - "SettingsGpuBackendRestartMessage": "Grafik Motoru ya da GPU ayarları değiştirildi. Bu işlemin uygulanması için yeniden başlatma gerekli.", - "SettingsGpuBackendRestartSubMessage": "Şimdi yeniden başlatmak istiyor musunuz?", - "RyujinxUpdaterMessage": "Ryujinx'i en son sürüme güncellemek ister misiniz?", - "SettingsTabHotkeysVolumeUpHotkey": "Sesi Arttır:", - "SettingsTabHotkeysVolumeDownHotkey": "Sesi Azalt:", - "SettingsEnableMacroHLE": "Macro HLE'yi Aktifleştir", - "SettingsEnableMacroHLETooltip": "GPU Macro kodunun yüksek seviye emülasyonu.\n\nPerformansı arttırır, ama bazı oyunlarda grafik hatalarına yol açabilir.\n\nEmin değilseniz AÇIK bırakın.", - "SettingsEnableColorSpacePassthrough": "Renk Alanı Geçişi", - "SettingsEnableColorSpacePassthroughTooltip": "Vulkan Backend'ini renk alanı belirtmeden renk bilgisinden geçmeye yönlendirir. Geniş gam ekranlı kullanıcılar için bu, renk doğruluğu pahasına daha canlı renklerle sonuçlanabilir.", - "VolumeShort": "Ses", - "UserProfilesManageSaves": "Kayıtları Yönet", - "DeleteUserSave": "Bu oyun için kullanıcı kaydını silmek istiyor musunuz?", - "IrreversibleActionNote": "Bu eylem geri alınamaz.", - "SaveManagerHeading": "{0} için Kayıt Dosyalarını Yönet", - "SaveManagerTitle": "Kayıt Yöneticisi", - "Name": "İsim", - "Size": "Boyut", - "Search": "Ara", - "UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar", - "Recover": "Kurtar", - "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", - "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", - "GraphicsAATooltip": "Oyuna Kenar Yumuşatma Ekler", - "GraphicsAALabel": "Kenar Yumuşatma:", - "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", - "GraphicsScalingFilterTooltip": "Çerçeve Arabellek Filtresini Açar", - "GraphicsScalingFilterLevelLabel": "Seviye", - "GraphicsScalingFilterLevelTooltip": "Ölçekleme Filtre Seviyesini Belirle", - "SmaaLow": "Düşük SMAA", - "SmaaMedium": "Orta SMAA", - "SmaaHigh": "Yüksek SMAA", - "SmaaUltra": "En Yüksek SMAA", - "UserEditorTitle": "Kullanıcıyı Düzenle", - "UserEditorTitleCreate": "Kullanıcı Oluştur", - "SettingsTabNetworkInterface": "Ağ Bağlantısı:", - "NetworkInterfaceTooltip": "LAN özellikleri için kullanılan ağ bağlantısı", - "NetworkInterfaceDefault": "Varsayılan", - "PackagingShaders": "Gölgeler Paketleniyor", - "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", - "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın" -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/uk_UA.json b/src/Ryujinx.Ava/Assets/Locales/uk_UA.json deleted file mode 100644 index a119fb4b..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/uk_UA.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "Yкраїнська", - "MenuBarFileOpenApplet": "Відкрити аплет", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", - "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", - "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", - "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", - "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", - "SettingsTabSystemUseHypervisor": "Use Hypervisor", - "MenuBarFile": "_Файл", - "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", - "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", - "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", - "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", - "MenuBarFileExit": "_Вихід", - "MenuBarOptions": "Опції", - "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", - "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", - "MenuBarOptionsStopEmulation": "Зупинити емуляцію", - "MenuBarOptionsSettings": "_Налаштування", - "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", - "MenuBarActions": "_Дії", - "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", - "MenuBarActionsScanAmiibo": "Сканувати Amiibo", - "MenuBarTools": "_Інструменти", - "MenuBarToolsInstallFirmware": "Встановити прошивку", - "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", - "MenuBarToolsManageFileTypes": "Manage file types", - "MenuBarToolsInstallFileTypes": "Install file types", - "MenuBarToolsUninstallFileTypes": "Uninstall file types", - "MenuBarHelp": "Довідка", - "MenuBarHelpCheckForUpdates": "Перевірити оновлення", - "MenuBarHelpAbout": "Про програму", - "MenuSearch": "Пошук...", - "GameListHeaderFavorite": "Вибране", - "GameListHeaderIcon": "Значок", - "GameListHeaderApplication": "Назва", - "GameListHeaderDeveloper": "Розробник", - "GameListHeaderVersion": "Версія", - "GameListHeaderTimePlayed": "Зіграно часу", - "GameListHeaderLastPlayed": "Остання гра", - "GameListHeaderFileExtension": "Розширення файлу", - "GameListHeaderFileSize": "Розмір файлу", - "GameListHeaderPath": "Шлях", - "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", - "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", - "GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми", - "GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків", - "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", - "GameListContextMenuManageDlc": "Керування DLC", - "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", - "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", - "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", - "GameListContextMenuCacheManagement": "Керування кешем", - "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", - "GameListContextMenuCacheManagementPurgeShaderCache": "Очистити кеш шейдерів", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Видаляє кеш шейдерів програми", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Відкрити каталог PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Відкриває каталог, який містить кеш PPTC програми", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Відкрити каталог кешу шейдерів", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Відкриває каталог, який містить кеш шейдерів програми", - "GameListContextMenuExtractData": "Видобути дані", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "Видобуває розділ ExeFS із поточної конфігурації програми (включаючи оновлення)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", - "GameListContextMenuExtractDataLogo": "Логотип", - "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", - "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", - "StatusBarSystemVersion": "Версія системи: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", - "Settings": "Налаштування", - "SettingsTabGeneral": "Інтерфейс користувача", - "SettingsTabGeneralGeneral": "Загальні", - "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", - "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", - "SettingsTabGeneralHideCursor": "Hide Cursor:", - "SettingsTabGeneralHideCursorNever": "Never", - "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", - "SettingsTabGeneralHideCursorAlways": "Always", - "SettingsTabGeneralGameDirectories": "Каталоги ігор", - "SettingsTabGeneralAdd": "Додати", - "SettingsTabGeneralRemove": "Видалити", - "SettingsTabSystem": "Система", - "SettingsTabSystemCore": "Ядро", - "SettingsTabSystemSystemRegion": "Регіон системи:", - "SettingsTabSystemSystemRegionJapan": "Японія", - "SettingsTabSystemSystemRegionUSA": "США", - "SettingsTabSystemSystemRegionEurope": "Європа", - "SettingsTabSystemSystemRegionAustralia": "Австралія", - "SettingsTabSystemSystemRegionChina": "Китай", - "SettingsTabSystemSystemRegionKorea": "Корея", - "SettingsTabSystemSystemRegionTaiwan": "Тайвань", - "SettingsTabSystemSystemLanguage": "Мова системи:", - "SettingsTabSystemSystemLanguageJapanese": "Японська", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Англійська (США)", - "SettingsTabSystemSystemLanguageFrench": "Французька", - "SettingsTabSystemSystemLanguageGerman": "Німецька", - "SettingsTabSystemSystemLanguageItalian": "Італійська", - "SettingsTabSystemSystemLanguageSpanish": "Іспанська", - "SettingsTabSystemSystemLanguageChinese": "Китайська", - "SettingsTabSystemSystemLanguageKorean": "Корейська", - "SettingsTabSystemSystemLanguageDutch": "Нідерландська", - "SettingsTabSystemSystemLanguagePortuguese": "Португальська", - "SettingsTabSystemSystemLanguageRussian": "Російська", - "SettingsTabSystemSystemLanguageTaiwanese": "Тайванська", - "SettingsTabSystemSystemLanguageBritishEnglish": "Англійська (Великобританія)", - "SettingsTabSystemSystemLanguageCanadianFrench": "Французька (Канада)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Іспанська (Латиноамериканська)", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Спрощена китайська", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська", - "SettingsTabSystemSystemTimeZone": "Часовий пояс системи:", - "SettingsTabSystemSystemTime": "Час системи:", - "SettingsTabSystemEnableVsync": "Вертикальна синхронізація", - "SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)", - "SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS", - "SettingsTabSystemAudioBackend": "Аудіосистема:", - "SettingsTabSystemAudioBackendDummy": "Dummy", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Хитрощі", - "SettingsTabSystemHacksNote": " (може викликати нестабільність)", - "SettingsTabSystemExpandDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", - "SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби", - "SettingsTabGraphics": "Графіка", - "SettingsTabGraphicsAPI": "Графічний API", - "SettingsTabGraphicsEnableShaderCache": "Увімкнути кеш шейдерів", - "SettingsTabGraphicsAnisotropicFiltering": "Анізотропна фільтрація:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Авто", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Роздільна здатність:", - "SettingsTabGraphicsResolutionScaleCustom": "Користувацька (не рекомендовано)", - "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", - "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", - "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", - "SettingsTabLogging": "Налагодження", - "SettingsTabLoggingLogging": "Налагодження", - "SettingsTabLoggingEnableLoggingToFile": "Увімкнути налагодження у файл", - "SettingsTabLoggingEnableStubLogs": "Увімкнути журнали заглушки", - "SettingsTabLoggingEnableInfoLogs": "Увімкнути інформаційні журнали", - "SettingsTabLoggingEnableWarningLogs": "Увімкнути журнали попереджень", - "SettingsTabLoggingEnableErrorLogs": "Увімкнути журнали помилок", - "SettingsTabLoggingEnableTraceLogs": "Увімкнути журнали трасування", - "SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей", - "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", - "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", - "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", - "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", - "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", - "SettingsTabLoggingEnableDebugLogs": "Увімкнути журнали налагодження", - "SettingsTabInput": "Введення", - "SettingsTabInputEnableDockedMode": "Режим док-станції", - "SettingsTabInputDirectKeyboardAccess": "Прямий доступ з клавіатури", - "SettingsButtonSave": "Зберегти", - "SettingsButtonClose": "Закрити", - "SettingsButtonOk": "Гаразд", - "SettingsButtonCancel": "Скасувати", - "SettingsButtonApply": "Застосувати", - "ControllerSettingsPlayer": "Гравець", - "ControllerSettingsPlayer1": "Гравець 1", - "ControllerSettingsPlayer2": "Гравець 2", - "ControllerSettingsPlayer3": "Гравець 3", - "ControllerSettingsPlayer4": "Гравець 4", - "ControllerSettingsPlayer5": "Гравець 5", - "ControllerSettingsPlayer6": "Гравець 6", - "ControllerSettingsPlayer7": "Гравець 7", - "ControllerSettingsPlayer8": "Гравець 8", - "ControllerSettingsHandheld": "Портативний", - "ControllerSettingsInputDevice": "Пристрій введення", - "ControllerSettingsRefresh": "Оновити", - "ControllerSettingsDeviceDisabled": "Вимкнено", - "ControllerSettingsControllerType": "Тип контролера", - "ControllerSettingsControllerTypeHandheld": "Портативний", - "ControllerSettingsControllerTypeProController": "Контролер Pro", - "ControllerSettingsControllerTypeJoyConPair": "Обидва JoyCon", - "ControllerSettingsControllerTypeJoyConLeft": "Лівий JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "Правий JoyCon", - "ControllerSettingsProfile": "Профіль", - "ControllerSettingsProfileDefault": "Типовий", - "ControllerSettingsLoad": "Завантажити", - "ControllerSettingsAdd": "Додати", - "ControllerSettingsRemove": "Видалити", - "ControllerSettingsButtons": "Кнопки", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Панель направлення", - "ControllerSettingsDPadUp": "Вгору", - "ControllerSettingsDPadDown": "Вниз", - "ControllerSettingsDPadLeft": "Вліво", - "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", - "ControllerSettingsLStick": "Лівий джойстик", - "ControllerSettingsRStick": "Правий джойстик", - "ControllerSettingsTriggersLeft": "Тригери ліворуч", - "ControllerSettingsTriggersRight": "Тригери праворуч", - "ControllerSettingsTriggersButtonsLeft": "Кнопки тригерів ліворуч", - "ControllerSettingsTriggersButtonsRight": "Кнопки тригерів праворуч", - "ControllerSettingsTriggers": "Тригери", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Кнопки ліворуч", - "ControllerSettingsExtraButtonsRight": "Кнопки праворуч", - "ControllerSettingsMisc": "Різне", - "ControllerSettingsTriggerThreshold": "Поріг спрацьовування:", - "ControllerSettingsMotion": "Рух", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Використовувати рух, сумісний з CemuHook", - "ControllerSettingsMotionControllerSlot": "Слот контролера:", - "ControllerSettingsMotionMirrorInput": "Дзеркальний вхід", - "ControllerSettingsMotionRightJoyConSlot": "Правий слот JoyCon:", - "ControllerSettingsMotionServerHost": "Хост сервера:", - "ControllerSettingsMotionGyroSensitivity": "Чутливість гіроскопа:", - "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", - "ControllerSettingsSave": "Зберегти", - "ControllerSettingsClose": "Закрити", - "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", - "UserProfilesSaveProfileName": "Зберегти ім'я профілю", - "UserProfilesChangeProfileImage": "Змінити зображення профілю", - "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", - "UserProfilesAddNewProfile": "Створити профіль", - "UserProfilesDelete": "Delete", - "UserProfilesClose": "Закрити", - "ProfileNameSelectionWatermark": "Choose a nickname", - "ProfileImageSelectionTitle": "Вибір зображення профілю", - "ProfileImageSelectionHeader": "Виберіть зображення профілю", - "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", - "ProfileImageSelectionImportImage": "Імпорт файлу зображення", - "ProfileImageSelectionSelectAvatar": "Виберіть аватар прошивки ", - "InputDialogTitle": "Діалог введення", - "InputDialogOk": "Гаразд", - "InputDialogCancel": "Скасувати", - "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", - "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", - "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", - "AvatarChoose": "Вибрати", - "AvatarSetBackgroundColor": "Встановити колір фону", - "AvatarClose": "Закрити", - "ControllerSettingsLoadProfileToolTip": "Завантажити профіль", - "ControllerSettingsAddProfileToolTip": "Додати профіль", - "ControllerSettingsRemoveProfileToolTip": "Видалити профіль", - "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", - "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", - "MenuBarFileToolsHideUi": "Сховати інтерфейс", - "GameListContextMenuRunApplication": "Run Application", - "GameListContextMenuToggleFavorite": "Перемкнути вибране", - "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", - "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Темна", - "SettingsTabGeneralThemeBaseStyleLight": "Світла", - "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", - "ButtonBrowse": "Огляд", - "ControllerSettingsConfigureGeneral": "Налаштування", - "ControllerSettingsRumble": "Вібрація", - "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", - "ControllerSettingsRumbleWeakMultiplier": "Множник слабкої вібрації", - "DialogMessageSaveNotAvailableMessage": "Немає збережених даних для {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Хочете створити дані збереження для цієї гри?", - "DialogConfirmationTitle": "Ryujinx - Підтвердження", - "DialogUpdaterTitle": "Ryujinx - Програма оновлення", - "DialogErrorTitle": "Ryujinx - Помилка", - "DialogWarningTitle": "Ryujinx - Попередження", - "DialogExitTitle": "Ryujinx - Вихід", - "DialogErrorMessage": "У Ryujinx сталася помилка", - "DialogExitMessage": "Ви впевнені, що бажаєте закрити Ryujinx?", - "DialogExitSubMessage": "Усі незбережені дані буде втрачено!", - "DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}", - "DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}", - "FolderDialogExtractTitle": "Виберіть папку для видобування", - "DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Екстрактор розділів NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.", - "DialogNcaExtractionCheckLogErrorMessage": "Помилка видобування. Прочитайте файл журналу для отримання додаткової інформації.", - "DialogNcaExtractionSuccessMessage": "Видобування успішно завершено.", - "DialogUpdaterConvertFailedMessage": "Не вдалося конвертувати поточну версію Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Скасування оновлення!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Ви вже використовуєте останню версію Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Під час спроби отримати інформацію про випуск із GitHub Release сталася помилка. Це може бути спричинено, якщо новий випуск компілюється GitHub Actions. Повторіть спробу через кілька хвилин.", - "DialogUpdaterConvertFailedGithubMessage": "Не вдалося конвертувати отриману версію Ryujinx із випуску Github.", - "DialogUpdaterDownloadingMessage": "Завантаження оновлення...", - "DialogUpdaterExtractionMessage": "Видобування оновлення...", - "DialogUpdaterRenamingMessage": "Перейменування оновлення...", - "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", - "DialogUpdaterCompleteMessage": "Оновлення завершено!", - "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", - "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", - "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", - "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", - "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", - "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "Будь ласка, завантажте Ryujinx на https://ryujinx.org/, якщо ви шукаєте підтримувану версію.", - "DialogRestartRequiredMessage": "Потрібен перезапуск", - "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", - "DialogThemeRestartSubMessage": "Ви хочете перезапустити", - "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", - "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", - "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", - "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", - "DialogInstallFileTypesErrorMessage": "Failed to install file types.", - "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", - "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", - "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", - "DialogControllerAppletTitle": "Аплет контролера", - "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", - "DialogErrorAppletErrorExceptionMessage": "Помилка показу діалогового вікна ErrorApplet: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nДля отримання додаткової інформації про те, як виправити цю помилку, дотримуйтесь нашого посібника з налаштування.", - "DialogUserErrorDialogTitle": "Помилка Ryujinx ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "Під час отримання інформації з API сталася помилка.", - "DialogAmiiboApiConnectErrorMessage": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.", - "DialogProfileInvalidProfileErrorMessage": "Профіль {0} несумісний із поточною системою конфігурації вводу.", - "DialogProfileDefaultProfileOverwriteErrorMessage": "Стандартний профіль не можна перезаписати", - "DialogProfileDeleteProfileTitle": "Видалення профілю", - "DialogProfileDeleteProfileMessage": "Цю дію неможливо скасувати. Ви впевнені, що бажаєте продовжити?", - "DialogWarning": "Увага", - "DialogPPTCDeletionMessage": "Ви збираєтеся видалити кеш PPTC для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", - "DialogPPTCDeletionErrorMessage": "Помилка очищення кешу PPTC на {0}: {1}", - "DialogShaderDeletionMessage": "Ви збираєтеся видалити кеш шейдерів для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", - "DialogShaderDeletionErrorMessage": "Помилка очищення кешу шейдерів на {0}: {1}", - "DialogRyujinxErrorMessage": "У Ryujinx сталася помилка", - "DialogInvalidTitleIdErrorMessage": "Помилка інтерфейсу: вибрана гра не мала дійсного ідентифікатора назви", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Дійсна прошивка системи не знайдена в {0}.", - "DialogFirmwareInstallerFirmwareInstallTitle": "Встановити прошивку {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Буде встановлено версію системи {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЦе замінить поточну версію системи {0}.", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nВи хочете продовжити?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", - "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", - "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", - "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", - "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", - "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", - "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", - "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", - "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", - "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Ви увімкнули скидання шейдерів, призначений лише для розробників.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути скидання шейдерів. Ви хочете вимкнути скидання шейдерів зараз?", - "DialogLoadAppGameAlreadyLoadedMessage": "Гру вже завантажено", - "DialogLoadAppGameAlreadyLoadedSubMessage": "Зупиніть емуляцію або закрийте емулятор перед запуском іншої гри.", - "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", - "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", - "SettingsTabGraphicsFeaturesOptions": "Особливості", - "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", - "CommonAuto": "Авто", - "CommonOff": "Вимкнути", - "CommonOn": "Увімкнути", - "InputDialogYes": "Так", - "InputDialogNo": "Ні", - "DialogProfileInvalidProfileNameErrorMessage": "Ім'я файлу містить неприпустимі символи. Будь ласка, спробуйте ще раз.", - "MenuBarOptionsPauseEmulation": "Пауза", - "MenuBarOptionsResumeEmulation": "Продовжити", - "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", - "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", - "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", - "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", - "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", - "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", - "AboutRyujinxAboutTitle": "Про програму:", - "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", - "AboutRyujinxMaintainersTitle": "Підтримується:", - "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", - "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", - "AmiiboSeriesLabel": "Серія Amiibo", - "AmiiboCharacterLabel": "Персонаж", - "AmiiboScanButtonLabel": "Сканувати", - "AmiiboOptionsShowAllLabel": "Показати всі Amiibo", - "AmiiboOptionsUsRandomTagLabel": "Хитрість: Використовувати випадковий тег Uuid", - "DlcManagerTableHeadingEnabledLabel": "Увімкнено", - "DlcManagerTableHeadingTitleIdLabel": "ID заголовка", - "DlcManagerTableHeadingContainerPathLabel": "Шлях до контейнеру", - "DlcManagerTableHeadingFullPathLabel": "Повний шлях", - "DlcManagerRemoveAllButton": "Видалити все", - "DlcManagerEnableAllButton": "Увімкнути всі", - "DlcManagerDisableAllButton": "Вимкнути всі", - "MenuBarOptionsChangeLanguage": "Змінити мову", - "MenuBarShowFileTypes": "Show File Types", - "CommonSort": "Сортувати", - "CommonShowNames": "Показати назви", - "CommonFavorite": "Вибрані", - "OrderAscending": "За зростанням", - "OrderDescending": "За спаданням", - "SettingsTabGraphicsFeatures": "Функції та вдосконалення", - "ErrorWindowTitle": "Вікно помилок", - "ToggleDiscordTooltip": "Виберіть, чи відображати Ryujinx у вашій «поточній грі» в Discord", - "AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку", - "AddGameDirTooltip": "Додати каталог гри до списку", - "RemoveGameDirTooltip": "Видалити вибраний каталог гри", - "CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора", - "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", - "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", - "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", - "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", - "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", - "RegionTooltip": "Змінити регіон системи", - "LanguageTooltip": "Змінити мову системи", - "TimezoneTooltip": "Змінити часовий пояс системи", - "TimeTooltip": "Змінити час системи", - "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", - "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", - "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", - "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", - "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", - "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", - "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", - "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", - "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", - "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", - "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", - "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", - "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", - "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", - "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", - "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", - "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", - "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", - "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", - "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", - "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", - "InfoLogTooltip": "Друкує повідомлення інформаційного журналу на консолі. Не впливає на продуктивність.", - "WarnLogTooltip": "Друкує повідомлення журналу попереджень у консолі. Не впливає на продуктивність.", - "ErrorLogTooltip": "Друкує повідомлення журналу помилок у консолі. Не впливає на продуктивність.", - "TraceLogTooltip": "Друкує повідомлення журналу трасування на консолі. Не впливає на продуктивність.", - "GuestLogTooltip": "Друкує повідомлення журналу гостей у консолі. Не впливає на продуктивність.", - "FileAccessLogTooltip": "Друкує повідомлення журналу доступу до файлів у консолі.", - "FSAccessLogModeTooltip": "Вмикає виведення журналу доступу до FS на консоль. Можливі режими 0-3", - "DeveloperOptionTooltip": "Використовуйте з обережністю", - "OpenGlLogLevel": "Потрібно увімкнути відповідні рівні журналу", - "DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.", - "LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch", - "LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження", - "OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx", - "OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали", - "ExitTooltip": "Виходить з Ryujinx", - "OpenSettingsTooltip": "Відкриває вікно налаштувань", - "OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів", - "StopEmulationTooltip": "Зупиняє емуляцію поточної гри та повертається до вибору гри", - "CheckUpdatesTooltip": "Перевіряє наявність оновлень для Ryujinx", - "OpenAboutTooltip": "Відкриває вікно «Про програму».", - "GridSize": "Розмір сітки", - "GridSizeTooltip": "Змінити розмір елементів сітки", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальська (Бразилія)", - "AboutRyujinxContributorsButtonHeader": "Переглянути всіх співавторів", - "SettingsTabSystemAudioVolume": "Гучність: ", - "AudioVolumeTooltip": "Змінити гучність звуку", - "SettingsTabSystemEnableInternetAccess": "Гостьовий доступ до Інтернету/режим LAN", - "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", - "GameListContextMenuManageCheatToolTip": "Керування читами", - "GameListContextMenuManageCheat": "Керування читами", - "ControllerSettingsStickRange": "Діапазон:", - "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", - "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", - "SettingsTabCpu": "ЦП", - "SettingsTabAudio": "Аудіо", - "SettingsTabNetwork": "Мережа", - "SettingsTabNetworkConnection": "Підключення до мережі", - "SettingsTabCpuCache": "Кеш ЦП", - "SettingsTabCpuMemory": "Пам'ять ЦП", - "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", - "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", - "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", - "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", - "IconSize": "Розмір значка", - "IconSizeTooltip": "Змінити розмір значків гри", - "MenuBarOptionsShowConsole": "Показати консоль", - "ShaderCachePurgeError": "Помилка очищення кешу шейдера {0}: {1}", - "UserErrorNoKeys": "Ключі не знайдено", - "UserErrorNoFirmware": "Прошивка не знайдена", - "UserErrorFirmwareParsingFailed": "Помилка аналізу прошивки", - "UserErrorApplicationNotFound": "Додаток не знайдено", - "UserErrorUnknown": "Невідома помилка", - "UserErrorUndefined": "Невизначена помилка", - "UserErrorNoKeysDescription": "Ryujinx не вдалося знайти ваш файл «prod.keys».", - "UserErrorNoFirmwareDescription": "Ryujinx не вдалося знайти встановлену прошивку", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.", - "UserErrorApplicationNotFoundDescription": "Ryujinx не вдалося знайти дійсний додаток за вказаним шляхом", - "UserErrorUnknownDescription": "Сталася невідома помилка!", - "UserErrorUndefinedDescription": "Сталася невизначена помилка! Цього не повинно статися, зверніться до розробника!", - "OpenSetupGuideMessage": "Відкрити посібник із налаштування", - "NoUpdate": "Немає оновлень", - "TitleUpdateVersionLabel": "Версія {0} - {1}", - "RyujinxInfo": "Ryujin x - Інформація", - "RyujinxConfirm": "Ryujinx - Підтвердження", - "FileDialogAllTypes": "Всі типи", - "Never": "Ніколи", - "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", - "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", - "SoftwareKeyboard": "Програмна клавіатура", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", - "UpdaterRenaming": "Перейменування старих файлів...", - "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", - "UpdaterAddingFiles": "Додавання нових файлів...", - "UpdaterExtracting": "Видобування оновлення...", - "UpdaterDownloading": "Завантаження оновлення...", - "Game": "Гра", - "Docked": "Док-станція", - "Handheld": "Портативний", - "ConnectionError": "Помилка з'єднання.", - "AboutPageDeveloperListMore": "{0} та інші...", - "ApiError": "Помилка API.", - "LoadingHeading": "Завантаження {0}", - "CompilingPPTC": "Компіляція PTC", - "CompilingShaders": "Компіляція шейдерів", - "AllKeyboards": "Всі клавіатури", - "OpenFileDialogTitle": "Виберіть підтримуваний файл для відкриття", - "OpenFolderDialogTitle": "Виберіть теку з розпакованою грою", - "AllSupportedFormats": "Усі підтримувані формати", - "RyujinxUpdater": "Програма оновлення Ryujinx", - "SettingsTabHotkeys": "Гарячі клавіші клавіатури", - "SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури", - "SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:", - "SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:", - "SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:", - "SettingsTabHotkeysPauseHotkey": "Пауза:", - "SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:", - "ControllerMotionTitle": "Налаштування керування рухом", - "ControllerRumbleTitle": "Налаштування вібрації", - "SettingsSelectThemeFileDialogTitle": "Виберіть файл теми", - "SettingsXamlThemeFile": "Файл теми Xaml", - "AvatarWindowTitle": "Керування обліковими записами - Аватар", - "Amiibo": "Amiibo", - "Unknown": "Невідомо", - "Usage": "Використання", - "Writable": "Можливість запису", - "SelectDlcDialogTitle": "Виберіть файли DLC", - "SelectUpdateDialogTitle": "Виберіть файли оновлення", - "UserProfileWindowTitle": "Менеджер профілів користувачів", - "CheatWindowTitle": "Менеджер читів", - "DlcWindowTitle": "Менеджер вмісту для завантаження", - "UpdateWindowTitle": "Менеджер оновлення назв", - "CheatWindowHeading": "Коди доступні для {0} [{1}]", - "BuildId": "BuildId:", - "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", - "UserProfilesEditProfile": "Редагувати вибране", - "Cancel": "Скасувати", - "Save": "Зберегти", - "Discard": "Скасувати", - "UserProfilesSetProfileImage": "Встановити зображення профілю", - "UserProfileEmptyNameError": "Назва обов'язкова", - "UserProfileNoImageError": "Зображення профілю обов'язкове", - "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", - "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", - "UserProfilesName": "Ім'я", - "UserProfilesUserId": "ID користувача:", - "SettingsTabGraphicsBackend": "Графічний сервер", - "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", - "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", - "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", - "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", - "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", - "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", - "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", - "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", - "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", - "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", - "SettingsEnableMacroHLE": "Увімкнути макрос HLE", - "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Гуч", - "UserProfilesManageSaves": "Керувати збереженнями", - "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", - "IrreversibleActionNote": "Цю дію не можна скасувати.", - "SaveManagerHeading": "Керувати збереженнями для {0}", - "SaveManagerTitle": "Менеджер збереження", - "Name": "Назва", - "Size": "Розмір", - "Search": "Пошук", - "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", - "Recover": "Відновити", - "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", - "UserProfilesRecoverEmptyList": "No profiles to recover", - "GraphicsAATooltip": "Applies anti-aliasing to the game render", - "GraphicsAALabel": "Anti-Aliasing:", - "GraphicsScalingFilterLabel": "Scaling Filter:", - "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", - "GraphicsScalingFilterLevelLabel": "Level", - "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", - "SmaaLow": "SMAA Low", - "SmaaMedium": "SMAA Medium", - "SmaaHigh": "SMAA High", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Edit User", - "UserEditorTitleCreate": "Create User", - "SettingsTabNetworkInterface": "Network Interface:", - "NetworkInterfaceTooltip": "The network interface used for LAN features", - "NetworkInterfaceDefault": "Default", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json deleted file mode 100644 index d09a80ec..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "简体中文", - "MenuBarFileOpenApplet": "打开小程序", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", - "SettingsTabInputDirectMouseAccess": "直通鼠标操作", - "SettingsTabSystemMemoryManagerMode": "内存管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "软件", - "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", - "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", - "MenuBarFile": "文件", - "MenuBarFileOpenFromFile": "加载文件", - "MenuBarFileOpenUnpacked": "加载解包后的游戏", - "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", - "MenuBarFileOpenLogsFolder": "打开日志文件夹", - "MenuBarFileExit": "退出", - "MenuBarOptions": "选项", - "MenuBarOptionsToggleFullscreen": "切换全屏", - "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", - "MenuBarOptionsStopEmulation": "停止模拟", - "MenuBarOptionsSettings": "设置", - "MenuBarOptionsManageUserProfiles": "管理用户账户", - "MenuBarActions": "操作", - "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", - "MenuBarActionsScanAmiibo": "扫描 Amiibo", - "MenuBarTools": "工具", - "MenuBarToolsInstallFirmware": "安装固件", - "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", - "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", - "MenuBarToolsManageFileTypes": "管理文件扩展名", - "MenuBarToolsInstallFileTypes": "关联文件扩展名", - "MenuBarToolsUninstallFileTypes": "取消关联扩展名", - "MenuBarHelp": "帮助", - "MenuBarHelpCheckForUpdates": "检查更新", - "MenuBarHelpAbout": "关于", - "MenuSearch": "搜索…", - "GameListHeaderFavorite": "收藏", - "GameListHeaderIcon": "图标", - "GameListHeaderApplication": "名称", - "GameListHeaderDeveloper": "制作商", - "GameListHeaderVersion": "版本", - "GameListHeaderTimePlayed": "游玩时长", - "GameListHeaderLastPlayed": "最近游玩", - "GameListHeaderFileExtension": "扩展名", - "GameListHeaderFileSize": "大小", - "GameListHeaderPath": "路径", - "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", - "GameListContextMenuOpenDeviceSaveDirectory": "打开系统目录", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", - "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", - "GameListContextMenuManageTitleUpdates": "管理游戏更新", - "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", - "GameListContextMenuManageDlc": "管理 DLC", - "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", - "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", - "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", - "GameListContextMenuCacheManagement": "缓存管理", - "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", - "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", - "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", - "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含游戏着色器缓存的目录", - "GameListContextMenuExtractData": "提取数据", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", - "GameListContextMenuExtractDataLogo": "图标", - "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", - "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", - "StatusBarSystemVersion": "系统版本:{0}", - "LinuxVmMaxMapCountDialogTitle": "检测到内存映射的限制过低", - "LinuxVmMaxMapCountDialogTextPrimary": "你想要将 vm.max_map_count 的值增加到 {0} 吗", - "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会试图创建超过当前允许的内存映射数量。当超过此限制时,Ryujinx会立即崩溃。", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,关闭后重置", - "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", - "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", - "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会试图创建超过当前允许的内存映射量。当大于此限制时,Ryujinx 会立即崩溃。\n\n你可以手动增加内存映射限制或者安装 pkexec,它可以辅助Ryujinx解决该问题。", - "Settings": "设置", - "SettingsTabGeneral": "用户界面", - "SettingsTabGeneralGeneral": "常规", - "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", - "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", - "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", - "SettingsTabGeneralHideCursorNever": "从不", - "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", - "SettingsTabGeneralHideCursorAlways": "始终", - "SettingsTabGeneralGameDirectories": "游戏目录", - "SettingsTabGeneralAdd": "添加", - "SettingsTabGeneralRemove": "删除", - "SettingsTabSystem": "系统", - "SettingsTabSystemCore": "核心", - "SettingsTabSystemSystemRegion": "系统区域:", - "SettingsTabSystemSystemRegionJapan": "日本", - "SettingsTabSystemSystemRegionUSA": "美国", - "SettingsTabSystemSystemRegionEurope": "欧洲", - "SettingsTabSystemSystemRegionAustralia": "澳大利亚", - "SettingsTabSystemSystemRegionChina": "中国", - "SettingsTabSystemSystemRegionKorea": "韩国", - "SettingsTabSystemSystemRegionTaiwan": "台湾地区", - "SettingsTabSystemSystemLanguage": "系统语言:", - "SettingsTabSystemSystemLanguageJapanese": "日语", - "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", - "SettingsTabSystemSystemLanguageFrench": "法语", - "SettingsTabSystemSystemLanguageGerman": "德语", - "SettingsTabSystemSystemLanguageItalian": "意大利语", - "SettingsTabSystemSystemLanguageSpanish": "西班牙语", - "SettingsTabSystemSystemLanguageChinese": "简体中文", - "SettingsTabSystemSystemLanguageKorean": "韩语", - "SettingsTabSystemSystemLanguageDutch": "荷兰语", - "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", - "SettingsTabSystemSystemLanguageRussian": "俄语", - "SettingsTabSystemSystemLanguageTaiwanese": "繁体中文(台湾)", - "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", - "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", - "SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)", - "SettingsTabSystemSystemTimeZone": "系统时区:", - "SettingsTabSystemSystemTime": "系统时钟:", - "SettingsTabSystemEnableVsync": "启用垂直同步", - "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", - "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", - "SettingsTabSystemAudioBackend": "音频后端:", - "SettingsTabSystemAudioBackendDummy": "无", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "音频输入/输出", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": "(会引起模拟器不稳定)", - "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", - "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", - "SettingsTabGraphics": "图形", - "SettingsTabGraphicsAPI": "图形 API", - "SettingsTabGraphicsEnableShaderCache": "启用着色器缓存", - "SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "自动", - "SettingsTabGraphicsAnisotropicFiltering2x": "2x", - "SettingsTabGraphicsAnisotropicFiltering4x": "4x", - "SettingsTabGraphicsAnisotropicFiltering8x": "8x", - "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "分辨率缩放:", - "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", - "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "宽高比:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "拉伸以适应窗口", - "SettingsTabGraphicsDeveloperOptions": "开发者选项", - "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", - "SettingsTabLogging": "日志", - "SettingsTabLoggingLogging": "日志", - "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", - "SettingsTabLoggingEnableStubLogs": "启用 Stub 日志", - "SettingsTabLoggingEnableInfoLogs": "启用信息日志", - "SettingsTabLoggingEnableWarningLogs": "启用警告日志", - "SettingsTabLoggingEnableErrorLogs": "启用错误日志", - "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", - "SettingsTabLoggingEnableGuestLogs": "启用来宾日志", - "SettingsTabLoggingEnableFsAccessLogs": "启用访问日志", - "SettingsTabLoggingFsGlobalAccessLogMode": "全局访问日志模式:", - "SettingsTabLoggingDeveloperOptions": "开发者选项", - "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低性能", - "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", - "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", - "SettingsTabLoggingEnableDebugLogs": "启用调试日志", - "SettingsTabInput": "输入", - "SettingsTabInputEnableDockedMode": "主机模式", - "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", - "SettingsButtonSave": "保存", - "SettingsButtonClose": "取消", - "SettingsButtonOk": "确定", - "SettingsButtonCancel": "取消", - "SettingsButtonApply": "应用", - "ControllerSettingsPlayer": "玩家", - "ControllerSettingsPlayer1": "玩家 1", - "ControllerSettingsPlayer2": "玩家 2", - "ControllerSettingsPlayer3": "玩家 3", - "ControllerSettingsPlayer4": "玩家 4", - "ControllerSettingsPlayer5": "玩家 5", - "ControllerSettingsPlayer6": "玩家 6", - "ControllerSettingsPlayer7": "玩家 7", - "ControllerSettingsPlayer8": "玩家 8", - "ControllerSettingsHandheld": "掌机模式", - "ControllerSettingsInputDevice": "输入设备", - "ControllerSettingsRefresh": "刷新", - "ControllerSettingsDeviceDisabled": "关闭", - "ControllerSettingsControllerType": "手柄类型", - "ControllerSettingsControllerTypeHandheld": "掌机", - "ControllerSettingsControllerTypeProController": "Pro 手柄", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon 组合", - "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "预设", - "ControllerSettingsProfileDefault": "默认布局", - "ControllerSettingsLoad": "加载", - "ControllerSettingsAdd": "新建", - "ControllerSettingsRemove": "删除", - "ControllerSettingsButtons": "按键", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "方向键", - "ControllerSettingsDPadUp": "上", - "ControllerSettingsDPadDown": "下", - "ControllerSettingsDPadLeft": "左", - "ControllerSettingsDPadRight": "右", - "ControllerSettingsStickButton": "按下摇杆", - "ControllerSettingsStickUp": "上", - "ControllerSettingsStickDown": "下", - "ControllerSettingsStickLeft": "左", - "ControllerSettingsStickRight": "右", - "ControllerSettingsStickStick": "摇杆", - "ControllerSettingsStickInvertXAxis": "反转 X 轴方向", - "ControllerSettingsStickInvertYAxis": "反转 Y 轴方向", - "ControllerSettingsStickDeadzone": "死区:", - "ControllerSettingsLStick": "左摇杆", - "ControllerSettingsRStick": "右摇杆", - "ControllerSettingsTriggersLeft": "左扳机", - "ControllerSettingsTriggersRight": "右扳机", - "ControllerSettingsTriggersButtonsLeft": "左扳机键", - "ControllerSettingsTriggersButtonsRight": "右扳机键", - "ControllerSettingsTriggers": "扳机", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "左背键", - "ControllerSettingsExtraButtonsRight": "右背键", - "ControllerSettingsMisc": "其他", - "ControllerSettingsTriggerThreshold": "扳机阈值:", - "ControllerSettingsMotion": "体感", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", - "ControllerSettingsMotionControllerSlot": "手柄:", - "ControllerSettingsMotionMirrorInput": "镜像操作", - "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", - "ControllerSettingsMotionServerHost": "服务器Host:", - "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", - "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", - "ControllerSettingsSave": "保存", - "ControllerSettingsClose": "关闭", - "UserProfilesSelectedUserProfile": "选择的用户账户:", - "UserProfilesSaveProfileName": "保存名称", - "UserProfilesChangeProfileImage": "更换头像", - "UserProfilesAvailableUserProfiles": "现有账户:", - "UserProfilesAddNewProfile": "新建账户", - "UserProfilesDelete": "删除", - "UserProfilesClose": "关闭", - "ProfileNameSelectionWatermark": "选择昵称", - "ProfileImageSelectionTitle": "选择头像", - "ProfileImageSelectionHeader": "选择合适的头像图片", - "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", - "ProfileImageSelectionImportImage": "导入图像文件", - "ProfileImageSelectionSelectAvatar": "选择系统头像", - "InputDialogTitle": "输入对话框", - "InputDialogOk": "完成", - "InputDialogCancel": "取消", - "InputDialogAddNewProfileTitle": "选择用户名称", - "InputDialogAddNewProfileHeader": "请输入账户名称", - "InputDialogAddNewProfileSubtext": "(最大长度:{0})", - "AvatarChoose": "选择头像", - "AvatarSetBackgroundColor": "设置背景色", - "AvatarClose": "关闭", - "ControllerSettingsLoadProfileToolTip": "加载预设", - "ControllerSettingsAddProfileToolTip": "新增预设", - "ControllerSettingsRemoveProfileToolTip": "删除预设", - "ControllerSettingsSaveProfileToolTip": "保存预设", - "MenuBarFileToolsTakeScreenshot": "保存截图", - "MenuBarFileToolsHideUi": "隐藏界面", - "GameListContextMenuRunApplication": "运行应用", - "GameListContextMenuToggleFavorite": "收藏", - "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", - "SettingsTabGeneralTheme": "主题", - "SettingsTabGeneralThemeCustomTheme": "自选主题路径", - "SettingsTabGeneralThemeBaseStyle": "主题色调", - "SettingsTabGeneralThemeBaseStyleDark": "暗黑", - "SettingsTabGeneralThemeBaseStyleLight": "浅色", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", - "ButtonBrowse": "浏览", - "ControllerSettingsConfigureGeneral": "配置", - "ControllerSettingsRumble": "震动", - "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", - "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", - "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", - "DialogConfirmationTitle": "Ryujinx - 设置", - "DialogUpdaterTitle": "Ryujinx - 更新", - "DialogErrorTitle": "Ryujinx - 错误", - "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 关闭", - "DialogErrorMessage": "Ryujinx 发生错误", - "DialogExitMessage": "是否关闭 Ryujinx?", - "DialogExitSubMessage": "未保存的进度将会丢失!", - "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错:{0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错:{0}", - "FolderDialogExtractTitle": "选择要解压到的文件夹", - "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", - "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", - "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过一会再试。", - "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下载新版本中...", - "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在删除旧文件...", - "DialogUpdaterAddingFilesMessage": "安装更新中...", - "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", - "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", - "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", - "DialogUpdaterNoInternetMessage": "没有连接到互联网", - "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", - "DialogRestartRequiredMessage": "需要重启模拟器", - "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", - "DialogThemeRestartSubMessage": "您是否要重启?", - "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", - "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", - "DialogFirmwareInstalledMessage": "已安装固件 {0}", - "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", - "DialogInstallFileTypesErrorMessage": "关联文件类型失败。", - "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", - "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败。", - "DialogOpenSettingsWindowLabel": "打开设置窗口", - "DialogControllerAppletTitle": "控制器小窗口", - "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", - "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", - "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", - "DialogAmiiboApiTitle": "Amiibo API", - "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", - "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", - "DialogProfileInvalidProfileErrorMessage": "预设 {0} 与当前输入配置系统不兼容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", - "DialogProfileDeleteProfileTitle": "删除预设", - "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", - "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", - "DialogPPTCDeletionErrorMessage": "清除位于 {0} 的 PPTC 缓存时出错:{1}", - "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", - "DialogShaderDeletionErrorMessage": "清除位于 {0} 的着色器缓存时出错:{1}", - "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", - "DialogInvalidTitleIdErrorMessage": "UI错误:所选游戏没有有效的标题ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径 {0} 找不到有效的系统固件。", - "DialogFirmwareInstallerFirmwareInstallTitle": "固件 {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", - "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", - "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", - "DialogUserProfileUnsavedChangesTitle": "警告 - 未保存的更改", - "DialogUserProfileUnsavedChangesMessage": "您为该用户做出的部分改动尚未保存。", - "DialogUserProfileUnsavedChangesSubMessage": "是否舍弃这些改动?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", - "DialogLoadNcaErrorMessage": "{0}. 错误的文件:{1}", - "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", - "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", - "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", - "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", - "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", - "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "多线程图形后端:", - "CommonAuto": "自动(推荐)", - "CommonOff": "关闭", - "CommonOn": "打开", - "InputDialogYes": "是", - "InputDialogNo": "否", - "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", - "MenuBarOptionsPauseEmulation": "暂停", - "MenuBarOptionsResumeEmulation": "继续", - "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 官网。", - "AboutDisclaimerMessage": "Ryujinx 以任何方式与 Nintendo™ 及其任何商业伙伴都没有关联", - "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", - "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", - "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", - "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", - "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", - "AboutRyujinxAboutTitle": "关于:", - "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", - "AboutRyujinxMaintainersTitle": "由以下作者维护:", - "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", - "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", - "AmiiboSeriesLabel": "Amiibo 系列", - "AmiiboCharacterLabel": "角色", - "AmiiboScanButtonLabel": "扫描", - "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", - "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", - "DlcManagerTableHeadingEnabledLabel": "启用", - "DlcManagerTableHeadingTitleIdLabel": "游戏ID", - "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", - "DlcManagerTableHeadingFullPathLabel": "完整路径", - "DlcManagerRemoveAllButton": "全部删除", - "DlcManagerEnableAllButton": "全部启用", - "DlcManagerDisableAllButton": "全部禁用", - "MenuBarOptionsChangeLanguage": "更改语言", - "MenuBarShowFileTypes": "主页显示的文件类型", - "CommonSort": "排序", - "CommonShowNames": "显示名称", - "CommonFavorite": "收藏", - "OrderAscending": "从小到大", - "OrderDescending": "从大到小", - "SettingsTabGraphicsFeatures": "功能与增强", - "ErrorWindowTitle": "错误窗口", - "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", - "AddGameDirBoxTooltip": "输入要添加的游戏目录", - "AddGameDirTooltip": "添加游戏目录到列表中", - "RemoveGameDirTooltip": "移除选中的目录", - "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", - "CustomThemePathTooltip": "自定义主题的目录", - "CustomThemeBrowseTooltip": "查找自定义主题", - "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型。", - "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", - "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", - "RegionTooltip": "更改系统区域", - "LanguageTooltip": "更改系统语言", - "TimezoneTooltip": "更改系统时区", - "TimeTooltip": "更改系统时钟", - "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定,就请保持开启状态。", - "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度。\n如不确定,就请保持开启状态。", - "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。\n如不确定,就请保持开启状态。", - "AudioBackendTooltip": "默认推荐SDL2,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端。", - "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗。", - "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢。", - "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译效率更高。", - "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,使得即时编译效率更高。\nRyujinx 可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)。", - "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译。在可用的情况下能大幅提高性能。但目前可能不稳定。", - "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", - "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭。", - "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "GalThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿。", - "ResolutionScaleTooltip": "缩放渲染的分辨率", - "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG。", - "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", - "AspectRatioTooltip": "渲染窗口的宽高比。", - "ShaderDumpPathTooltip": "转储图形着色器的路径", - "FileLogTooltip": "保存日志文件到硬盘。不会影响性能。", - "StubLogTooltip": "在控制台中打印 stub 日志消息。不影响性能。", - "InfoLogTooltip": "在控制台中打印信息日志消息。不影响性能。", - "WarnLogTooltip": "在控制台中打印警告日志消息。不影响性能。", - "ErrorLogTooltip": "打印控制台中的错误日志消息。不影响性能。", - "TraceLogTooltip": "在控制台中打印跟踪日志消息。不影响性能。", - "GuestLogTooltip": "在控制台中打印访客日志消息。不影响性能。", - "FileAccessLogTooltip": "在控制台中打印文件访问日志信息。", - "FSAccessLogModeTooltip": "启用访问日志输出到控制台。可能的模式为 0-3", - "DeveloperOptionTooltip": "谨慎使用", - "OpenGlLogLevel": "需要打开适当的日志等级", - "DebugLogTooltip": "记录Debug消息", - "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", - "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", - "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", - "OpenRyujinxLogsTooltip": "打开日志存放的目录", - "ExitTooltip": "关闭 Ryujinx", - "OpenSettingsTooltip": "打开设置窗口", - "OpenProfileManagerTooltip": "打开用户账户管理界面", - "StopEmulationTooltip": "停止运行当前游戏并回到主界面", - "CheckUpdatesTooltip": "检查 Ryujinx 新版本", - "OpenAboutTooltip": "打开“关于”窗口", - "GridSize": "网格尺寸", - "GridSizeTooltip": "调整网格模式的大小", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", - "AboutRyujinxContributorsButtonHeader": "查看所有参与者", - "SettingsTabSystemAudioVolume": "音量:", - "AudioVolumeTooltip": "调节音量", - "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", - "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实的 Switch 连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", - "GameListContextMenuManageCheatToolTip": "管理金手指", - "GameListContextMenuManageCheat": "管理金手指", - "ControllerSettingsStickRange": "范围:", - "DialogStopEmulationTitle": "Ryujinx - 停止模拟", - "DialogStopEmulationMessage": "是否确定停止模拟?", - "SettingsTabCpu": "CPU", - "SettingsTabAudio": "音频", - "SettingsTabNetwork": "网络", - "SettingsTabNetworkConnection": "网络连接", - "SettingsTabCpuCache": "CPU 缓存", - "SettingsTabCpuMemory": "CPU 内存", - "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已禁用!", - "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", - "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", - "ControllerSettingsRotate90": "顺时针旋转 90°", - "IconSize": "图标尺寸", - "IconSizeTooltip": "更改游戏图标大小", - "MenuBarOptionsShowConsole": "显示控制台", - "ShaderCachePurgeError": "清除着色器缓存时出错:{0}: {1}", - "UserErrorNoKeys": "找不到密钥", - "UserErrorNoFirmware": "找不到固件", - "UserErrorFirmwareParsingFailed": "固件解析错误", - "UserErrorApplicationNotFound": "找不到应用程序", - "UserErrorUnknown": "未知错误", - "UserErrorUndefined": "未定义错误", - "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于使用了过旧的密钥。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", - "UserErrorUnknownDescription": "发生未知错误!", - "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", - "OpenSetupGuideMessage": "打开设置教程", - "NoUpdate": "无更新", - "TitleUpdateVersionLabel": "版本 {0}", - "RyujinxInfo": "Ryujinx - 信息", - "RyujinxConfirm": "Ryujinx - 确认", - "FileDialogAllTypes": "全部类型", - "Never": "从不", - "SwkbdMinCharacters": "至少应为 {0} 个字长", - "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", - "SoftwareKeyboard": "软件键盘", - "SoftwareKeyboardModeNumbersOnly": "只接受数字", - "SoftwareKeyboardModeAlphabet": "只接受非中日韩文字", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符号", - "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", - "UpdaterRenaming": "正在删除旧文件...", - "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", - "UpdaterAddingFiles": "安装更新中...", - "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下载新版本中...", - "Game": "游戏", - "Docked": "主机模式", - "Handheld": "掌机模式", - "ConnectionError": "连接错误。", - "AboutPageDeveloperListMore": "{0} 等开发者...", - "ApiError": "API错误。", - "LoadingHeading": "正在启动 {0}", - "CompilingPPTC": "编译PPTC缓存中", - "CompilingShaders": "编译着色器中", - "AllKeyboards": "所有键盘", - "OpenFileDialogTitle": "选择一个支持的文件以打开", - "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", - "AllSupportedFormats": "所有支持的格式", - "RyujinxUpdater": "Ryujinx 更新程序", - "SettingsTabHotkeys": "快捷键", - "SettingsTabHotkeysHotkeys": "键盘快捷键", - "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截屏:", - "SettingsTabHotkeysShowUiHotkey": "隐藏 界面:", - "SettingsTabHotkeysPauseHotkey": "暂停:", - "SettingsTabHotkeysToggleMuteHotkey": "静音:", - "ControllerMotionTitle": "体感操作设置", - "ControllerRumbleTitle": "震动设置", - "SettingsSelectThemeFileDialogTitle": "选择主题文件", - "SettingsXamlThemeFile": "Xaml 主题文件", - "AvatarWindowTitle": "管理账户 - 头像", - "Amiibo": "Amiibo", - "Unknown": "未知", - "Usage": "扫描可获得", - "Writable": "可写入", - "SelectDlcDialogTitle": "选择 DLC 文件", - "SelectUpdateDialogTitle": "选择更新文件", - "UserProfileWindowTitle": "管理用户账户", - "CheatWindowTitle": "金手指管理器", - "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", - "UpdateWindowTitle": "游戏更新管理器", - "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", - "BuildId": "游戏版本ID:", - "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", - "UserProfilesEditProfile": "编辑选中账户", - "Cancel": "取消", - "Save": "保存", - "Discard": "返回", - "UserProfilesSetProfileImage": "选择头像", - "UserProfileEmptyNameError": "必须输入名称", - "UserProfileNoImageError": "请选择您的头像", - "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", - "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", - "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", - "UserProfilesName": "名称:", - "UserProfilesUserId": "用户ID:", - "SettingsTabGraphicsBackend": "图形后端", - "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", - "SettingsEnableTextureRecompression": "启用纹理重压缩", - "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU 开启。\n如果您不确定,请保持此项关闭。", - "SettingsTabGraphicsPreferredGpu": "首选 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan API 使用的显卡。\n此选项不会影响 OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项。", - "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", - "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", - "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", - "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", - "SettingsTabHotkeysVolumeUpHotkey": "音量加:", - "SettingsTabHotkeysVolumeDownHotkey": "音量减:", - "SettingsEnableMacroHLE": "启用 HLE 宏", - "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项开启。", - "SettingsEnableColorSpacePassthrough": "颜色空间穿透", - "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 后端在不指定颜色空间的情况下传递颜色信息。对于具有宽色域显示器的用户来说,这可能会以颜色正确性为代价,产生更鲜艳的颜色。", - "VolumeShort": "音量", - "UserProfilesManageSaves": "管理存档", - "DeleteUserSave": "确定删除这个游戏的存档吗?", - "IrreversibleActionNote": "删除后不可恢复。", - "SaveManagerHeading": "管理 {0} ({1}) 的存档", - "SaveManagerTitle": "存档管理器", - "Name": "名称", - "Size": "大小", - "Search": "搜索", - "UserProfilesRecoverLostAccounts": "恢复丢失的账户", - "Recover": "恢复", - "UserProfilesRecoverHeading": "找到了这些用户的存档数据", - "UserProfilesRecoverEmptyList": "没有可以恢复的配置文件", - "GraphicsAATooltip": "将抗锯齿使用到游戏渲染中", - "GraphicsAALabel": "抗锯齿:", - "GraphicsScalingFilterLabel": "缩放过滤:", - "GraphicsScalingFilterTooltip": "对帧缓冲区进行缩放", - "GraphicsScalingFilterLevelLabel": "等级", - "GraphicsScalingFilterLevelTooltip": "设置缩放过滤级别", - "SmaaLow": "SMAA 低质量", - "SmaaMedium": "SMAA 中质量", - "SmaaHigh": "SMAA 高质量", - "SmaaUltra": "SMAA 极致质量", - "UserEditorTitle": "编辑用户", - "UserEditorTitleCreate": "创建用户", - "SettingsTabNetworkInterface": "网络接口:", - "NetworkInterfaceTooltip": "用于局域网功能的网络接口", - "NetworkInterfaceDefault": "默认", - "PackagingShaders": "整合着色器中", - "AboutChangelogButton": "在Github上查看更新日志", - "AboutChangelogButtonTooltipMessage": "点击这里在您的默认浏览器中打开此版本的更新日志。" -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json deleted file mode 100644 index a2f59f60..00000000 --- a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "Language": "英文 (美國)", - "MenuBarFileOpenApplet": "開啟 Applet 應用程序", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "開啟獨立的Mii修改器應用程序", - "SettingsTabInputDirectMouseAccess": "滑鼠直接操作", - "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "軟體", - "SettingsTabSystemMemoryManagerModeHost": "主機模式 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "主機略過檢查模式 (最快, 但不安全)", - "SettingsTabSystemUseHypervisor": "使用 Hypervisor", - "MenuBarFile": "_檔案", - "MenuBarFileOpenFromFile": "_載入檔案", - "MenuBarFileOpenUnpacked": "載入_已解開封裝的遊戲", - "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", - "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", - "MenuBarFileExit": "_退出", - "MenuBarOptions": "選項", - "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", - "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", - "MenuBarOptionsStopEmulation": "停止模擬", - "MenuBarOptionsSettings": "_設定", - "MenuBarOptionsManageUserProfiles": "_管理使用者帳戶", - "MenuBarActions": "_動作", - "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", - "MenuBarActionsScanAmiibo": "掃描 Amiibo", - "MenuBarTools": "_工具", - "MenuBarToolsInstallFirmware": "安裝韌體", - "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", - "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", - "MenuBarToolsManageFileTypes": "管理檔案類型", - "MenuBarToolsInstallFileTypes": "註冊檔案類型", - "MenuBarToolsUninstallFileTypes": "取消註冊檔案類型", - "MenuBarHelp": "幫助", - "MenuBarHelpCheckForUpdates": "檢查更新", - "MenuBarHelpAbout": "關於", - "MenuSearch": "搜尋...", - "GameListHeaderFavorite": "收藏", - "GameListHeaderIcon": "圖示", - "GameListHeaderApplication": "名稱", - "GameListHeaderDeveloper": "開發人員", - "GameListHeaderVersion": "版本", - "GameListHeaderTimePlayed": "遊玩時數", - "GameListHeaderLastPlayed": "最近遊玩", - "GameListHeaderFileExtension": "副檔名", - "GameListHeaderFileSize": "檔案大小", - "GameListHeaderPath": "路徑", - "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此遊戲的存檔資料夾", - "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此遊戲的系統設定資料夾", - "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此遊戲的 BCAT 資料夾\n", - "GameListContextMenuManageTitleUpdates": "管理遊戲更新", - "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", - "GameListContextMenuManageDlc": "管理 DLC", - "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", - "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", - "GameListContextMenuOpenModsDirectoryToolTip": "開啟此遊戲的模組資料夾", - "GameListContextMenuCacheManagement": "快取管理", - "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", - "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", - "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的著色器快取", - "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此遊戲的 PPTC 快取資料夾", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此遊戲的著色器快取資料夾", - "GameListContextMenuExtractData": "提取資料", - "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", - "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", - "GameListContextMenuExtractDataLogo": "圖示", - "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", - "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", - "StatusBarSystemVersion": "系統版本: {0}", - "LinuxVmMaxMapCountDialogTitle": "檢測到映射的記憶體上限過低", - "LinuxVmMaxMapCountDialogTextPrimary": "你願意增加 vm.max_map_count to {0} 的數值嗎?", - "LinuxVmMaxMapCountDialogTextSecondary": "遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "碓定 (直至下一次重新啟動)", - "LinuxVmMaxMapCountDialogButtonPersistent": "碓定 (永遠設定)", - "LinuxVmMaxMapCountWarningTextPrimary": "映射記憶體的最大值少於目前建議的下限.", - "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值少於 {1}. 遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.\n\n你可能需要手動增加上限或安裝 pkexec, 這些都能協助Ryujinx完成此操作.", - "Settings": "設定", - "SettingsTabGeneral": "使用者介面", - "SettingsTabGeneralGeneral": "一般", - "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", - "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", - "SettingsTabGeneralHideCursor": "隱藏滑鼠遊標:", - "SettingsTabGeneralHideCursorNever": "永不", - "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", - "SettingsTabGeneralHideCursorAlways": "總是", - "SettingsTabGeneralGameDirectories": "遊戲資料夾", - "SettingsTabGeneralAdd": "新增", - "SettingsTabGeneralRemove": "刪除", - "SettingsTabSystem": "系統", - "SettingsTabSystemCore": "核心", - "SettingsTabSystemSystemRegion": "系統區域:", - "SettingsTabSystemSystemRegionJapan": "日本", - "SettingsTabSystemSystemRegionUSA": "美國", - "SettingsTabSystemSystemRegionEurope": "歐洲", - "SettingsTabSystemSystemRegionAustralia": "澳洲", - "SettingsTabSystemSystemRegionChina": "中國", - "SettingsTabSystemSystemRegionKorea": "韓國", - "SettingsTabSystemSystemRegionTaiwan": "台灣", - "SettingsTabSystemSystemLanguage": "系統語言:", - "SettingsTabSystemSystemLanguageJapanese": "日文", - "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", - "SettingsTabSystemSystemLanguageFrench": "法文", - "SettingsTabSystemSystemLanguageGerman": "德文", - "SettingsTabSystemSystemLanguageItalian": "義大利文", - "SettingsTabSystemSystemLanguageSpanish": "西班牙文", - "SettingsTabSystemSystemLanguageChinese": "中文 (中國)", - "SettingsTabSystemSystemLanguageKorean": "韓文", - "SettingsTabSystemSystemLanguageDutch": "荷蘭文", - "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙文", - "SettingsTabSystemSystemLanguageRussian": "俄文", - "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", - "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", - "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉丁美洲西班牙文", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", - "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文", - "SettingsTabSystemSystemTimeZone": "系統時區:", - "SettingsTabSystemSystemTime": "系統時鐘:", - "SettingsTabSystemEnableVsync": "垂直同步", - "SettingsTabSystemEnablePptc": "啟用 PPTC 快取", - "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", - "SettingsTabSystemAudioBackend": "音效處理後台架構:", - "SettingsTabSystemAudioBackendDummy": "模擬", - "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "SoundIO", - "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", - "SettingsTabSystemExpandDramSize": "使用額外的記憶體佈局 (開發人員)", - "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", - "SettingsTabGraphics": "圖像", - "SettingsTabGraphicsAPI": "圖像處理應用程式介面", - "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", - "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", - "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", - "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", - "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", - "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", - "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "螢幕長寬比例:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "伸展至螢幕大小", - "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器轉存路徑:", - "SettingsTabLogging": "日誌", - "SettingsTabLoggingLogging": "日誌", - "SettingsTabLoggingEnableLoggingToFile": "儲存記錄日誌為檔案", - "SettingsTabLoggingEnableStubLogs": "啟用 Stub 記錄", - "SettingsTabLoggingEnableInfoLogs": "啟用資訊記錄", - "SettingsTabLoggingEnableWarningLogs": "啟用警告記錄", - "SettingsTabLoggingEnableErrorLogs": "啟用錯誤記錄", - "SettingsTabLoggingEnableTraceLogs": "啟用追蹤記錄", - "SettingsTabLoggingEnableGuestLogs": "啟用賓客記錄", - "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案存取記錄", - "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", - "SettingsTabLoggingDeveloperOptions": "開發者選項", - "SettingsTabLoggingDeveloperOptionsNote": "警告:此操作會降低效能", - "SettingsTabLoggingGraphicsBackendLogLevel": "圖像處理後台記錄等級:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", - "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", - "SettingsTabLoggingEnableDebugLogs": "啟用除錯記錄", - "SettingsTabInput": "輸入", - "SettingsTabInputEnableDockedMode": "Docked 模式", - "SettingsTabInputDirectKeyboardAccess": "鍵盤直接操作", - "SettingsButtonSave": "儲存", - "SettingsButtonClose": "關閉", - "SettingsButtonOk": "確定", - "SettingsButtonCancel": "取消", - "SettingsButtonApply": "套用", - "ControllerSettingsPlayer": "玩家", - "ControllerSettingsPlayer1": "玩家 1", - "ControllerSettingsPlayer2": "玩家 2", - "ControllerSettingsPlayer3": "玩家 3", - "ControllerSettingsPlayer4": "玩家 4", - "ControllerSettingsPlayer5": "玩家 5", - "ControllerSettingsPlayer6": "玩家 6", - "ControllerSettingsPlayer7": "玩家 7", - "ControllerSettingsPlayer8": "玩家 8", - "ControllerSettingsHandheld": "掌機模式", - "ControllerSettingsInputDevice": "輸入裝置", - "ControllerSettingsRefresh": "更新", - "ControllerSettingsDeviceDisabled": "關閉", - "ControllerSettingsControllerType": "控制器類型", - "ControllerSettingsControllerTypeHandheld": "掌機", - "ControllerSettingsControllerTypeProController": "Nintendo Switch Pro控制器", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon", - "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "配置檔案", - "ControllerSettingsProfileDefault": "預設", - "ControllerSettingsLoad": "載入", - "ControllerSettingsAdd": "建立", - "ControllerSettingsRemove": "刪除", - "ControllerSettingsButtons": "按鍵", - "ControllerSettingsButtonA": "A", - "ControllerSettingsButtonB": "B", - "ControllerSettingsButtonX": "X", - "ControllerSettingsButtonY": "Y", - "ControllerSettingsButtonPlus": "+", - "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "方向鍵", - "ControllerSettingsDPadUp": "上", - "ControllerSettingsDPadDown": "下", - "ControllerSettingsDPadLeft": "左", - "ControllerSettingsDPadRight": "右", - "ControllerSettingsStickButton": "按鍵", - "ControllerSettingsStickUp": "上", - "ControllerSettingsStickDown": "下", - "ControllerSettingsStickLeft": "左", - "ControllerSettingsStickRight": "右", - "ControllerSettingsStickStick": "搖桿", - "ControllerSettingsStickInvertXAxis": "搖桿左右反向", - "ControllerSettingsStickInvertYAxis": "搖桿上下反向", - "ControllerSettingsStickDeadzone": "盲區:", - "ControllerSettingsLStick": "左搖桿", - "ControllerSettingsRStick": "右搖桿", - "ControllerSettingsTriggersLeft": "左 Triggers", - "ControllerSettingsTriggersRight": "右 Triggers", - "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", - "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", - "ControllerSettingsTriggers": "板機", - "ControllerSettingsTriggerL": "L", - "ControllerSettingsTriggerR": "R", - "ControllerSettingsTriggerZL": "ZL", - "ControllerSettingsTriggerZR": "ZR", - "ControllerSettingsLeftSL": "SL", - "ControllerSettingsLeftSR": "SR", - "ControllerSettingsRightSL": "SL", - "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "左按鍵", - "ControllerSettingsExtraButtonsRight": "右按鍵", - "ControllerSettingsMisc": "其他", - "ControllerSettingsTriggerThreshold": "Triggers 閾值:", - "ControllerSettingsMotion": "傳感器", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 相容性傳感協定", - "ControllerSettingsMotionControllerSlot": "控制器插槽:", - "ControllerSettingsMotionMirrorInput": "鏡像輸入", - "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", - "ControllerSettingsMotionServerHost": "伺服器IP地址:", - "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", - "ControllerSettingsMotionGyroDeadzone": "陀螺儀盲區:", - "ControllerSettingsSave": "儲存", - "ControllerSettingsClose": "關閉", - "UserProfilesSelectedUserProfile": "選擇使用者帳戶:", - "UserProfilesSaveProfileName": "儲存帳戶名稱", - "UserProfilesChangeProfileImage": "更換帳戶頭像", - "UserProfilesAvailableUserProfiles": "現有的使用者帳戶:", - "UserProfilesAddNewProfile": "建立帳戶", - "UserProfilesDelete": "刪除", - "UserProfilesClose": "關閉", - "ProfileNameSelectionWatermark": "選擇一個暱稱", - "ProfileImageSelectionTitle": "帳戶頭像選擇", - "ProfileImageSelectionHeader": "選擇帳戶頭像", - "ProfileImageSelectionNote": "你可以導入自訂頭像,或從系統中選擇頭像", - "ProfileImageSelectionImportImage": "導入圖片檔案", - "ProfileImageSelectionSelectAvatar": "選擇系統頭像", - "InputDialogTitle": "輸入對話框", - "InputDialogOk": "完成", - "InputDialogCancel": "取消", - "InputDialogAddNewProfileTitle": "選擇帳戶名稱", - "InputDialogAddNewProfileHeader": "請輸入帳戶名稱", - "InputDialogAddNewProfileSubtext": "(最大長度:{0})", - "AvatarChoose": "選擇", - "AvatarSetBackgroundColor": "設定背景顏色", - "AvatarClose": "關閉", - "ControllerSettingsLoadProfileToolTip": "載入配置檔案", - "ControllerSettingsAddProfileToolTip": "新增配置檔案", - "ControllerSettingsRemoveProfileToolTip": "刪除配置檔案", - "ControllerSettingsSaveProfileToolTip": "儲存配置檔案", - "MenuBarFileToolsTakeScreenshot": "儲存截圖", - "MenuBarFileToolsHideUi": "隱藏使用者介面", - "GameListContextMenuRunApplication": "執行程式", - "GameListContextMenuToggleFavorite": "標記為收藏", - "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", - "SettingsTabGeneralTheme": "佈景主題", - "SettingsTabGeneralThemeCustomTheme": "自訂佈景主題路徑", - "SettingsTabGeneralThemeBaseStyle": "基本佈景主題式樣", - "SettingsTabGeneralThemeBaseStyleDark": "深色模式", - "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂佈景主題", - "ButtonBrowse": "瀏覽", - "ControllerSettingsConfigureGeneral": "配置", - "ControllerSettingsRumble": "震動", - "ControllerSettingsRumbleStrongMultiplier": "強震動調節", - "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", - "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", - "DialogConfirmationTitle": "Ryujinx - 設定", - "DialogUpdaterTitle": "Ryujinx - 更新", - "DialogErrorTitle": "Ryujinx - 錯誤", - "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 關閉", - "DialogErrorMessage": "Ryujinx 遇到了錯誤", - "DialogExitMessage": "你確定要關閉 Ryujinx 嗎?", - "DialogExitSubMessage": "所有未儲存的資料將會遺失!", - "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出現錯誤: {0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出現錯誤: {0}", - "FolderDialogExtractTitle": "選擇要解壓到的資料夾", - "DialogNcaExtractionMessage": "提取{1}的{0}分區...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", - "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", - "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "你使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時失敗。可能是因為 GitHub Actions 正在編譯新版本。請於數分數後重試。", - "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下載最新版本中...", - "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", - "DialogUpdaterAddingFilesMessage": "安裝更新中...", - "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "你確定要立即重新啟動 Ryujinx 嗎?", - "DialogUpdaterArchNotSupportedMessage": "你執行的系統架構不被支援!", - "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", - "DialogUpdaterNoInternetMessage": "你沒有連接到網際網絡!", - "DialogUpdaterNoInternetSubMessage": "請確保網際網絡連接正常!", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果你希望使用被受支援的Ryujinx版本,請你在官方網址 https://ryujinx.org/ 下載.", - "DialogRestartRequiredMessage": "模擬器必須重新啟動", - "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能生效。", - "DialogThemeRestartSubMessage": "你確定要現在重新啟動嗎?", - "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", - "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", - "DialogFirmwareInstalledMessage": "已安裝韌體{0}", - "DialogInstallFileTypesSuccessMessage": "成功註冊檔案類型!", - "DialogInstallFileTypesErrorMessage": "註冊檔案類型失敗。", - "DialogUninstallFileTypesSuccessMessage": "成功取消註冊檔案類型!", - "DialogUninstallFileTypesErrorMessage": "取消註冊檔案類型失敗。", - "DialogOpenSettingsWindowLabel": "開啟設定視窗", - "DialogControllerAppletTitle": "控制器小視窗", - "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出現錯誤: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", - "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出現錯誤: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", - "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", - "DialogAmiiboApiTitle": "Amiibo 應用程式介面", - "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", - "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或你沒有連接到網際網路。", - "DialogProfileInvalidProfileErrorMessage": "配置檔案 {0} 與目前輸入系統不相容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", - "DialogProfileDeleteProfileTitle": "刪除帳戶", - "DialogProfileDeleteProfileMessage": "此操作不可撤銷, 您確定要繼續嗎?", - "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "下一次重啟時將會重新建立以下遊戲的 PPTC 快取\n\n{0}\n\n你確定要繼續嗎?", - "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", - "DialogShaderDeletionMessage": "即將刪除以下遊戲的著色器快取:\n\n{0}\n\n你確定要繼續嗎?", - "DialogShaderDeletionErrorMessage": "清除{0}的著色器快取時出現錯誤: {1}", - "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", - "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", - "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "你確定要繼續嗎?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", - "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳戶", - "DialogUserProfileDeletionConfirmMessage": "你確定要刪除選擇中的帳戶嗎?", - "DialogUserProfileUnsavedChangesTitle": "警告 - 有未儲存的更改", - "DialogUserProfileUnsavedChangesMessage": "你對此帳戶所做的更改尚未儲存.", - "DialogUserProfileUnsavedChangesSubMessage": "你確定要捨棄更改嗎?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入配置檔案已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "你確定要儲存嗎?", - "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", - "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "你啟用了跟蹤記錄,它的設計僅限開發人員使用。", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用追蹤記錄。你是否要立即停用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "你啟用了著色器轉存,它的設計僅限開發人員使用。", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用著色器轉存。你是否要立即停用?", - "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入此遊戲", - "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", - "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 後台多工執行中", - "DialogSettingsBackendThreadingWarningMessage": "更改此選項後必須重啟 Ryujinx 才能生效。根據你的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GPU多執行緒。", - "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "圖像處理後台多線程支援:", - "CommonAuto": "自動(推薦)", - "CommonOff": "關閉", - "CommonOn": "打開", - "InputDialogYes": "是", - "InputDialogNo": "否", - "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", - "MenuBarOptionsPauseEmulation": "暫停", - "MenuBarOptionsResumeEmulation": "繼續", - "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官方網站。", - "AboutDisclaimerMessage": "Ryujinx 與 Nintendo™ 並沒有任何關聯, 包括其合作伙伴, 及任何形式上。", - "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", - "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助網頁。", - "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", - "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", - "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", - "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是 Nintendo Switch™ 的一款模擬器。\n懇請您在 Patreon 上贊助我們。\n關注 Twitter 或 Discord 可以取得我們的最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", - "AboutRyujinxMaintainersTitle": "開發及維護名單:", - "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", - "AboutRyujinxSupprtersTitle": "Patreon 的贊助人:", - "AmiiboSeriesLabel": "Amiibo 系列", - "AmiiboCharacterLabel": "角色", - "AmiiboScanButtonLabel": "掃描", - "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", - "AmiiboOptionsUsRandomTagLabel": "侵略:使用隨機標記的 Uuid 編碼", - "DlcManagerTableHeadingEnabledLabel": "已啟用", - "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", - "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", - "DlcManagerTableHeadingFullPathLabel": "完整路徑", - "DlcManagerRemoveAllButton": "全部刪除", - "DlcManagerEnableAllButton": "啟用全部", - "DlcManagerDisableAllButton": "停用全部", - "MenuBarOptionsChangeLanguage": "更改語言", - "MenuBarShowFileTypes": "顯示檔案類型", - "CommonSort": "排序", - "CommonShowNames": "顯示名稱", - "CommonFavorite": "收藏", - "OrderAscending": "從小到大", - "OrderDescending": "從大到小", - "SettingsTabGraphicsFeatures": "功能及優化", - "ErrorWindowTitle": "錯誤視窗", - "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", - "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", - "AddGameDirTooltip": "添加遊戲資料夾到列表中", - "RemoveGameDirTooltip": "移除選擇中的遊戲資料夾", - "CustomThemeCheckTooltip": "啟用或關閉自訂佈景主題", - "CustomThemePathTooltip": "自訂佈景主題的資料夾", - "CustomThemeBrowseTooltip": "查找自訂佈景主題", - "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", - "DirectKeyboardTooltip": "支援鍵盤直接存取 (HID協定) . 可供給遊戲使用你的鍵盤作為輸入文字裝置.", - "DirectMouseTooltip": "支援滑鼠直接存取 (HID協定) . 可供給遊戲使用你的滑鼠作為瞄準裝置.", - "RegionTooltip": "更改系統區域", - "LanguageTooltip": "更改系統語言", - "TimezoneTooltip": "更改系統時區", - "TimeTooltip": "更改系統時鐘", - "VSyncToggleTooltip": "模擬遊戲主機垂直同步更新頻率. 重要地反映著遊戲本身的速度; 關閉它可能會令後使用動態更新率的遊戲速度過高, 或會引致載入錯誤等等.\n\n可在遊戲中利用自訂快速鍵開關此功能. 我們也建議使用快速鍵, 如果你計劃關上它.\n\n如果不確定請設定為\"開啟\".", - "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", - "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", - "AudioBackendTooltip": "更改音效處理後台架構.\n\n推薦使用SDL2架構, 而OpenAL及SoundIO架構用作後備. Dummy是沒有音效的.\n\n如果不確定請設定為\"SDL2\".", - "MemoryManagerTooltip": "更改模擬器記憶體至電腦記憶體的映射和存取方式,極其影響CPU效能.\n\n如果不確定, 請設定為\"主機略過檢查模式\".", - "MemoryManagerSoftwareTooltip": "使用軟體虛擬分頁表換算, 最精確但是速度最慢.", - "MemoryManagerHostTooltip": "直接地映射模擬記憶體到電腦記憶體. 對 JIT 編譯和執行效率有顯著提升. ", - "MemoryManagerUnsafeTooltip": "直接地映射模擬記憶體到電腦記憶體, 但是不檢查記憶體溢出. 由於 Ryujinx 的子程式可以存取任何位置的記憶體, 因而相對不安全. 故在此模式下只應執行你信任的遊戲或軟體.", - "UseHypervisorTooltip": "使用 Hypervisor 代替 JIT。在本功能可用時就可以大幅增大效能,但目前狀態還不穩定。", - "DRamTooltip": "利用可選擇性的記憶體模式來模擬Switch發展中型號.\n\n此選項只會對高畫質材質包或4K模組有用. 而這並不會提升效能. \n\n如果不確定請關閉本功能.", - "IgnoreMissingServicesTooltip": "忽略某些未被實施的系統服務. 此功能有助於繞過當啟動遊戲時帶來的故障.\n\n如果不確定請關閉本功能。", - "GraphicsBackendThreadingTooltip": "執行雙線程後台繪圖指令, 能夠減少著色器編譯斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "GalThreadingTooltip": "執行雙線程後台繪圖指令.\n\n能夠加速著色器編譯及減少斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "ShaderCacheToggleTooltip": "儲存著色器快取到硬碟,減少存取斷續。\n\n如果不確定請設定為\"開啟\"。", - "ResolutionScaleTooltip": "解析度繪圖倍率", - "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", - "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", - "AspectRatioTooltip": "模擬器視窗解析度的長寬比", - "ShaderDumpPathTooltip": "圖形著色器轉存路徑", - "FileLogTooltip": "是否儲存日誌檔案到硬碟", - "StubLogTooltip": "在控制台顯示及記錄 Stub 訊息", - "InfoLogTooltip": "在控制台顯示及記錄資訊訊息", - "WarnLogTooltip": "在控制台顯示及記錄警告訊息\n", - "ErrorLogTooltip": "在控制台顯示及記錄錯誤訊息", - "TraceLogTooltip": "在控制台顯示及記錄追蹤訊息", - "GuestLogTooltip": "在控制台顯示及記錄賓客訊息", - "FileAccessLogTooltip": "在控制台顯示及記錄檔案存取訊息", - "FSAccessLogModeTooltip": "在控制台顯示及記錄FS 存取訊息. 可選的模式是 0-3", - "DeveloperOptionTooltip": "使用請謹慎", - "OpenGlLogLevel": "需要打開適當的日誌等級", - "DebugLogTooltip": "在控制台顯示及記錄除錯訊息.\n\n僅限於受訓的成員使用, 因為它很難理解而且令模擬的效能非常地差.\n", - "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", - "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", - "OpenRyujinxFolderTooltip": "開啟 Ryujinx 系統資料夾", - "OpenRyujinxLogsTooltip": "開啟存放日誌的資料夾", - "ExitTooltip": "關閉 Ryujinx", - "OpenSettingsTooltip": "開啟設定視窗", - "OpenProfileManagerTooltip": "開啟使用者帳戶管理視窗", - "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", - "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", - "OpenAboutTooltip": "開啟關於視窗", - "GridSize": "網格尺寸", - "GridSizeTooltip": "調整網格模式的大小", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", - "AboutRyujinxContributorsButtonHeader": "查看所有參與者", - "SettingsTabSystemAudioVolume": "音量:", - "AudioVolumeTooltip": "調節音量", - "SettingsTabSystemEnableInternetAccess": "啟用網路連接", - "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", - "GameListContextMenuManageCheatToolTip": "管理金手指", - "GameListContextMenuManageCheat": "管理金手指", - "ControllerSettingsStickRange": "範圍:", - "DialogStopEmulationTitle": "Ryujinx - 停止模擬", - "DialogStopEmulationMessage": "你確定要停止模擬嗎?", - "SettingsTabCpu": "處理器", - "SettingsTabAudio": "音訊", - "SettingsTabNetwork": "網路", - "SettingsTabNetworkConnection": "網路連接", - "SettingsTabCpuCache": "CPU 快取", - "SettingsTabCpuMemory": "CPU 模式", - "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已停用!", - "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", - "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此遊戲額外的SD記憶卡Atmosphere模組資料夾. 有用於包裝在真實主機上的模組.\n", - "ControllerSettingsRotate90": "順時針旋轉 90°", - "IconSize": "圖示尺寸", - "IconSizeTooltip": "更改遊戲圖示大小", - "MenuBarOptionsShowConsole": "顯示控制台", - "ShaderCachePurgeError": "清除 {0} 著色器快取時出現錯誤: {1}", - "UserErrorNoKeys": "找不到金鑰", - "UserErrorNoFirmware": "找不到韌體", - "UserErrorFirmwareParsingFailed": "韌體解析錯誤", - "UserErrorApplicationNotFound": "找不到應用程式", - "UserErrorUnknown": "未知錯誤", - "UserErrorUndefined": "未定義錯誤", - "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", - "UserErrorUnknownDescription": "發生未知錯誤!", - "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", - "OpenSetupGuideMessage": "開啟設定教學", - "NoUpdate": "沒有新版本", - "TitleUpdateVersionLabel": "版本 {0} - {1}", - "RyujinxInfo": "Ryujinx - 訊息", - "RyujinxConfirm": "Ryujinx - 確認", - "FileDialogAllTypes": "全部類型", - "Never": "從不", - "SwkbdMinCharacters": "至少應為 {0} 個字長", - "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", - "SoftwareKeyboard": "軟體鍵盤", - "SoftwareKeyboardModeNumbersOnly": "只接受數字", - "SoftwareKeyboardModeAlphabet": "不支援中日韓統一表意文字字元", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符號", - "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", - "UpdaterRenaming": "正在重新命名舊檔案...", - "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", - "UpdaterAddingFiles": "安裝更新中...", - "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下載新版本中...", - "Game": "遊戲", - "Docked": "主機模式", - "Handheld": "掌機模式", - "ConnectionError": "連接錯誤。", - "AboutPageDeveloperListMore": "{0} 等開發者...", - "ApiError": "API 錯誤", - "LoadingHeading": "正在啟動 {0}", - "CompilingPPTC": "編譯 PPTC 快取中", - "CompilingShaders": "編譯著色器中", - "AllKeyboards": "所有鍵盤", - "OpenFileDialogTitle": "選擇支援的檔案格式", - "OpenFolderDialogTitle": "選擇一個包含已解開封裝遊戲的資料夾\n", - "AllSupportedFormats": "全部支援的格式", - "RyujinxUpdater": "Ryujinx 更新程式", - "SettingsTabHotkeys": "快捷鍵", - "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", - "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截圖:", - "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面:", - "SettingsTabHotkeysPauseHotkey": "暫停:", - "SettingsTabHotkeysToggleMuteHotkey": "靜音:", - "ControllerMotionTitle": "體感操作設定", - "ControllerRumbleTitle": "震動設定", - "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", - "SettingsXamlThemeFile": "Xaml 主題檔案", - "AvatarWindowTitle": "管理帳號 - 頭貼", - "Amiibo": "Amiibo", - "Unknown": "未知", - "Usage": "用途", - "Writable": "可寫入", - "SelectDlcDialogTitle": "選擇 DLC 檔案", - "SelectUpdateDialogTitle": "選擇更新檔", - "UserProfileWindowTitle": "管理使用者帳戶", - "CheatWindowTitle": "管理遊戲金手指", - "DlcWindowTitle": "管理遊戲 DLC", - "UpdateWindowTitle": "管理遊戲更新", - "CheatWindowHeading": "金手指可用於 {0} [{1}]", - "BuildId": "版本編號:", - "DlcWindowHeading": "DLC 可用於 {0} [{1}]", - "UserProfilesEditProfile": "編輯所選", - "Cancel": "取消", - "Save": "儲存", - "Discard": "放棄更改", - "UserProfilesSetProfileImage": "設定帳戶頭像", - "UserProfileEmptyNameError": "使用者名稱為必填", - "UserProfileNoImageError": "必須設定帳戶頭像", - "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", - "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", - "UserProfilesName": "使用者名稱:", - "UserProfilesUserId": "使用者 ID:", - "SettingsTabGraphicsBackend": "圖像處理後台架構", - "SettingsTabGraphicsBackendTooltip": "用來圖像處理的後台架構", - "SettingsEnableTextureRecompression": "開啟材質重新壓縮", - "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", - "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "選擇支持運行Vulkan圖像處理架構的GPU.\n\n此設定不會影響GPU運行OpenGL.\n\n如果不確定, 請設定標籤為\"dGPU\"的GPU. 如果沒有, 請保留原始設定.", - "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", - "SettingsGpuBackendRestartMessage": "圖像處理後台架構或GPU相關設定已被修改。需要重新啟動才能套用。", - "SettingsGpuBackendRestartSubMessage": "你確定要現在重新啟動嗎?", - "RyujinxUpdaterMessage": "你確定要將 Ryujinx 更新到最新版本嗎?", - "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", - "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", - "SettingsEnableMacroHLE": "啟用 Macro HLE", - "SettingsEnableMacroHLETooltip": "GPU 微代碼的高階模擬。\n\n可以提升效能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請設定為\"開啟\"。", - "SettingsEnableColorSpacePassthrough": "色彩空間直通", - "SettingsEnableColorSpacePassthroughTooltip": "指揮Vulkan後端直通色彩資訊而不需指定色彩空間,對於廣色域顯示的使用者,這會造成較多抖色,犧牲色彩正確性。", - "VolumeShort": "音量", - "UserProfilesManageSaves": "管理遊戲存檔", - "DeleteUserSave": "你確定要刪除此遊戲的存檔嗎?", - "IrreversibleActionNote": "本動作將無法挽回。", - "SaveManagerHeading": "管理 {0} 的遊戲存檔", - "SaveManagerTitle": "遊戲存檔管理器", - "Name": "名稱", - "Size": "大小", - "Search": "搜尋", - "UserProfilesRecoverLostAccounts": "恢復遺失的帳戶", - "Recover": "恢復", - "UserProfilesRecoverHeading": "在以下帳戶找到了一些遊戲存檔", - "UserProfilesRecoverEmptyList": "沒有可恢復的使用者帳戶", - "GraphicsAATooltip": "在遊戲繪圖上套用抗鋸齒", - "GraphicsAALabel": "抗鋸齒:", - "GraphicsScalingFilterLabel": "縮放過濾器:", - "GraphicsScalingFilterTooltip": "啟用畫幀緩衝區縮放", - "GraphicsScalingFilterLevelLabel": "記錄檔等級", - "GraphicsScalingFilterLevelTooltip": "設定縮放過濾器的強度", - "SmaaLow": "低階 SMAA", - "SmaaMedium": "中階 SMAA", - "SmaaHigh": "高階 SMAA", - "SmaaUltra": "超高階 SMAA", - "UserEditorTitle": "編輯使用者", - "UserEditorTitleCreate": "建立使用者", - "SettingsTabNetworkInterface": "網路介面:", - "NetworkInterfaceTooltip": "用於具有 LAN 功能的網路介面", - "NetworkInterfaceDefault": "預設", - "PackagingShaders": "著色器封裝", - "AboutChangelogButton": "在 GitHub 查看更新日誌", - "AboutChangelogButtonTooltipMessage": "在瀏覽器中打開此Ryujinx版本的更新日誌。" -} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Styles/Styles.xaml b/src/Ryujinx.Ava/Assets/Styles/Styles.xaml deleted file mode 100644 index f7f64be2..00000000 --- a/src/Ryujinx.Ava/Assets/Styles/Styles.xaml +++ /dev/null @@ -1,396 +0,0 @@ -<Styles - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> - <Design.PreviewWith> - <Border Height="2000" - Padding="20"> - <StackPanel Spacing="5"> - <TextBlock Text="Code Font Family" /> - <Grid RowDefinitions="*,Auto"> - <Menu Grid.Row="1" - Width="100"> - <MenuItem Header="File"> - <MenuItem Header="Test 1" /> - <MenuItem Header="Test 2" /> - <MenuItem Header="Test 3"> - <MenuItem.Icon> - <CheckBox Margin="0" - IsChecked="{ReflectionBinding Checkbox, Mode=TwoWay}" /> - </MenuItem.Icon> - </MenuItem> - </MenuItem> - </Menu> - <StackPanel Orientation="Horizontal"> - <ToggleButton - Name="btnAdd" - Height="28" - HorizontalAlignment="Right" - Content="Addy" /> - <Button - Name="btnRem" - HorizontalAlignment="Right" - Content="Add" /> - <TextBox - Width="100" - VerticalAlignment="Center" - Text="Rrrrr" - UseFloatingWatermark="True" - Watermark="Hello" /> - <CheckBox>Test Check</CheckBox> - </StackPanel> - </Grid> - <ui:NumberBox Value="1" /> - </StackPanel> - </Border> - </Design.PreviewWith> - <Style Selector="Border.small"> - <Setter Property="Width" - Value="100" /> - </Style> - <Style Selector="Border.normal"> - <Setter Property="Width" - Value="130" /> - </Style> - <Style Selector="Border.large"> - <Setter Property="Width" - Value="160" /> - </Style> - <Style Selector="Border.huge"> - <Setter Property="Width" - Value="200" /> - </Style> - <Style Selector="Border.settings"> - <Setter Property="Background" - Value="{DynamicResource ThemeDarkColor}" /> - <Setter Property="BorderBrush" - Value="{DynamicResource MenuFlyoutPresenterBorderColor}" /> - <Setter Property="BorderThickness" - Value="1" /> - <Setter Property="CornerRadius" - Value="5" /> - </Style> - <Style Selector="Image.small"> - <Setter Property="Width" - Value="50" /> - </Style> - <Style Selector="Image.normal"> - <Setter Property="Width" - Value="80" /> - </Style> - <Style Selector="Image.large"> - <Setter Property="Width" - Value="100" /> - </Style> - <Style Selector="Image.huge"> - <Setter Property="Width" - Value="120" /> - </Style> - <Style Selector="#TitleBarHost > Image"> - <Setter Property="Margin" - Value="10" /> - </Style> - <Style Selector="#TitleBarHost > Label"> - <Setter Property="Margin" - Value="5" /> - <Setter Property="FontSize" - Value="14" /> - </Style> - <Style Selector="Button.SystemCaption"> - <Setter Property="MinWidth" - Value="10" /> - </Style> - <Style Selector="DataGridColumnHeader"> - <Setter Property="Foreground" - Value="{DynamicResource ThemeForegroundBrush}" /> - <Setter Property="HorizontalContentAlignment" - Value="Center" /> - <Setter Property="BorderThickness" - Value="1" /> - <Setter Property="VerticalContentAlignment" - Value="Center" /> - <Setter Property="SeparatorBrush" - Value="{DynamicResource ThemeControlBorderColor}" /> - <Setter Property="Padding" - Value="5" /> - <Setter Property="Background" - Value="{DynamicResource ThemeContentBackgroundColor}" /> - <Setter Property="Template"> - <ControlTemplate> - <Grid Background="{TemplateBinding Background}" - ColumnDefinitions="*,Auto"> - <Grid - Margin="{TemplateBinding Padding}" - HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" - VerticalAlignment="{TemplateBinding VerticalContentAlignment}" - ColumnDefinitions="*,Auto"> - <ContentPresenter Content="{TemplateBinding Content}" /> - <Path - Name="SortIcon" - Grid.Column="1" - Width="8" - Margin="4,0,0,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z " - Fill="{TemplateBinding Foreground}" - Stretch="Uniform" /> - </Grid> - <Rectangle - Name="VerticalSeparator" - Grid.Column="1" - Width="1" - VerticalAlignment="Stretch" - Fill="{TemplateBinding SeparatorBrush}" - IsVisible="{TemplateBinding AreSeparatorsVisible}" /> - </Grid> - </ControlTemplate> - </Setter> - </Style> - <Style Selector="DataGrid"> - <Setter Property="RowBackground" - Value="{DynamicResource ThemeAccentBrush4}" /> - <Setter Property="Background" - Value="{DynamicResource ThemeBackgroundBrush}" /> - <Setter Property="BorderBrush" - Value="{DynamicResource ThemeBorderLowColor}" /> - <Setter Property="BorderThickness" - Value="{DynamicResource ThemeBorderThickness}" /> - </Style> - <Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle"> - <Setter Property="Fill" - Value="{DynamicResource SystemAccentColor}" /> - <Setter Property="Opacity" - Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" /> - </Style> - <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle"> - <Setter Property="Fill" - Value="{DynamicResource SystemListLowColor}" /> - </Style> - <Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle"> - <Setter Property="Fill" - Value="{DynamicResource SystemAccentColor}" /> - <Setter Property="Opacity" - Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" /> - </Style> - <Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle"> - <Setter Property="Fill" - Value="{DynamicResource SystemAccentColor}" /> - <Setter Property="Opacity" - Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" /> - </Style> - <Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle"> - <Setter Property="Fill" - Value="{DynamicResource SystemAccentColor}" /> - <Setter Property="Opacity" - Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" /> - </Style> - <Style Selector="DataGridCell"> - <Setter Property="HorizontalAlignment" - Value="Center" /> - <Setter Property="HorizontalContentAlignment" - Value="Center" /> - </Style> - <Style Selector="DataGridCell.Left"> - <Setter Property="HorizontalAlignment" - Value="Left" /> - </Style> - <Style Selector="CheckBox"> - <Setter Property="BorderThickness" - Value="1" /> - </Style> - - <Style Selector="MenuItem"> - <Setter Property="Height" - Value="{DynamicResource MenuItemHeight}" /> - <Setter Property="Padding" - Value="{DynamicResource MenuItemPadding}" /> - <Setter Property="FontSize" - Value="12" /> - </Style> - <Style Selector="MenuItem:selected /template/ Border#root"> - <Setter Property="Background" - Value="{DynamicResource ThemeControlBorderColor}" /> - <Setter Property="BorderBrush" - Value="{DynamicResource ThemeControlBorderColor}" /> - </Style> - <Style Selector="TabItem > ScrollViewer"> - <Setter Property="Background" - Value="{DynamicResource ThemeBackgroundColor}" /> - <Setter Property="Margin" - Value="0,-5,0,0" /> - </Style> - <Style Selector="TabItem > ScrollViewer > Border"> - <Setter Property="BorderThickness" - Value="0,1,0,0" /> - <Setter Property="Background" - Value="{DynamicResource ThemeBackgroundColor}" /> - <Setter Property="BorderBrush" - Value="{DynamicResource HighlightBrush}" /> - </Style> - <Style Selector="Button"> - <Setter Property="MinWidth" - Value="80" /> - </Style> - <Style Selector="ProgressBar /template/ Border#ProgressBarTrack"> - <Setter Property="IsVisible" - Value="False" /> - </Style> - <Style Selector="ToggleButton"> - <Setter Property="Padding" - Value="0,-5,0,0" /> - </Style> - <Style Selector="TabItem"> - <Setter Property="FontSize" - Value="14" /> - <Setter Property="BorderThickness" - Value="0,0,1,0" /> - <Setter Property="BorderBrush" - Value="{DynamicResource ThemeButtonForegroundColor}" /> - <Setter Property="Background" - Value="{DynamicResource SystemAccentColorLight2}" /> - </Style> - <Style Selector="TabItem:pointerover"> - <Setter Property="Foreground" - Value="{DynamicResource ThemeButtonForegroundColor}" /> - </Style> - <Style Selector="TabItem:selected"> - <Setter Property="Background" - Value="{DynamicResource SystemAccentColorLight2}" /> - <Setter Property="Foreground" - Value="{DynamicResource ThemeBackgroundColor}" /> - </Style> - <Style Selector="TextBlock"> - <Setter Property="Margin" - Value="{DynamicResource TextMargin}" /> - <Setter Property="FontSize" - Value="{DynamicResource FontSize}" /> - <Setter Property="VerticalAlignment" - Value="Center" /> - <Setter Property="TextWrapping" - Value="WrapWithOverflow" /> - </Style> - <Style Selector="TextBlock.h1"> - <Setter Property="Margin" - Value="{DynamicResource TextMargin}" /> - <Setter Property="VerticalAlignment" - Value="Center" /> - <Setter Property="FontWeight" - Value="Bold" /> - <Setter Property="FontSize" - Value="16" /> - <Setter Property="TextWrapping" - Value="WrapWithOverflow" /> - </Style> - <Style Selector="Separator"> - <Setter Property="Background" - Value="{DynamicResource ThemeControlBorderColor}" /> - <Setter Property="Foreground" - Value="{DynamicResource ThemeControlBorderColor}" /> - <Setter Property="MinHeight" - Value="1" /> - </Style> - <Style Selector=":is(Button).DateTimeFlyoutButtonStyle"> - <Setter Property="Background" - Value="{DynamicResource SystemAccentColorLight2}" /> - <Setter Property="Foreground" - Value="{DynamicResource ThemeBackgroundColor}" /> - </Style> - <Style Selector="DatePickerPresenter"> - <Setter Property="BorderThickness" - Value="1" /> - <Setter Property="BorderBrush" - Value="{DynamicResource ThemeButtonForegroundColor}" /> - </Style> - <Style Selector="DataGridCell"> - <Setter Property="FontSize" - Value="14" /> - </Style> - <Style Selector="CheckBox TextBlock"> - <Setter Property="Margin" - Value="0,5,0,0" /> - </Style> - <Style Selector="ContextMenu"> - <Setter Property="BorderBrush" - Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" /> - <Setter Property="BorderThickness" - Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" /> - </Style> - <Style Selector="TextBox"> - <Setter Property="VerticalContentAlignment" - Value="Center" /> - </Style> - <Style Selector="TextBox.NumberBoxTextBoxStyle"> - <Setter Property="Foreground" - Value="{DynamicResource ThemeForegroundColor}" /> - </Style> - <Style Selector="ListBox ListBoxItem"> - <Setter Property="Padding" - Value="0" /> - <Setter Property="Margin" - Value="0" /> - <Setter Property="CornerRadius" - Value="5" /> - <Setter Property="Background" - Value="{DynamicResource AppListBackgroundColor}" /> - <Setter Property="BorderThickness" - Value="2"/> - </Style> - <Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter"> - <Setter Property="Background" - Value="{DynamicResource AppListBackgroundColor}" /> - </Style> - <Style Selector="ListBox"> - <Setter Property="Background" - Value="{DynamicResource ThemeContentBackgroundColor}" /> - </Style> - <Style Selector="FlyoutPresenter, ContextMenu, MenuFlyoutPresenter"> - <Setter Property="BorderBrush" - Value="{DynamicResource MenuFlyoutPresenterBorderColor}" /> - </Style> - <Style Selector="ListBox ListBoxItem:pointerover /template/ ContentPresenter"> - <Setter Property="Background" - Value="{DynamicResource AppListHoverBackgroundColor}" /> - </Style> - <Styles.Resources> - <SolidColorBrush x:Key="ThemeAccentColorBrush" - Color="{DynamicResource SystemAccentColor}" /> - <StaticResource x:Key="ListViewItemBackgroundSelected" - ResourceKey="ThemeAccentColorBrush" /> - <StaticResource x:Key="ListViewItemBackgroundPressed" - ResourceKey="SystemAccentColorDark1" /> - <StaticResource x:Key="ListViewItemBackgroundPointerOver" - ResourceKey="SystemAccentColorDark2" /> - <StaticResource x:Key="ListViewItemBackgroundSelectedPressed" - ResourceKey="ThemeAccentColorBrush" /> - <StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" - ResourceKey="SystemAccentColorDark2" /> - <SolidColorBrush - x:Key="DataGridGridLinesBrush" - Opacity="0.4" - Color="{DynamicResource SystemBaseMediumLowColor}" /> - <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" - Color="{DynamicResource DataGridSelectionColor}" /> - <SolidColorBrush x:Key="SplitButtonBackgroundChecked" - Color="#00E81123" /> - <SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver" - Color="#00E81123" /> - <SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed" - Color="#00E81123" /> - <SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" - Color="#00E81123" /> - <Thickness x:Key="PageMargin">40 0 40 0</Thickness> - <Thickness x:Key="Margin">0 5 0 5</Thickness> - <Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness> - <x:Double x:Key="ScrollBarThickness">15</x:Double> - <x:Double x:Key="FontSizeSmall">8</x:Double> - <x:Double x:Key="FontSizeNormal">10</x:Double> - <x:Double x:Key="FontSize">12</x:Double> - <x:Double x:Key="FontSizeLarge">15</x:Double> - <x:Double x:Key="ControlContentThemeFontSize">13</x:Double> - <x:Double x:Key="MenuItemHeight">26</x:Double> - <x:Double x:Key="TabItemMinHeight">28</x:Double> - <x:Double x:Key="ContentDialogMaxWidth">600</x:Double> - <x:Double x:Key="ContentDialogMaxHeight">756</x:Double> - </Styles.Resources> -</Styles> \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Styles/Themes.xaml b/src/Ryujinx.Ava/Assets/Styles/Themes.xaml deleted file mode 100644 index 0f323f84..00000000 --- a/src/Ryujinx.Ava/Assets/Styles/Themes.xaml +++ /dev/null @@ -1,85 +0,0 @@ -<ResourceDictionary xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - <ResourceDictionary.ThemeDictionaries> - <ResourceDictionary x:Key="Default"> - <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" - Color="{DynamicResource DataGridSelectionColor}" /> - <SolidColorBrush x:Key="ThemeAccentColorBrush" - Color="{DynamicResource SystemAccentColor}" /> - <SolidColorBrush x:Key="ThemeAccentBrush4" - Color="{DynamicResource ThemeAccentColor4}" /> - <Color x:Key="SystemAccentColor">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color> - <Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color> - <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> - <Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color> - <Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color> - <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> - <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> - <Color x:Key="ThemeForegroundColor">#FF000000</Color> - <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> - <Color x:Key="AppListBackgroundColor">#b3ffffff</Color> - <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> - <Color x:Key="SecondaryTextColor">#A0000000</Color> - <Color x:Key="VsyncEnabled">#FF2EEAC9</Color> - <Color x:Key="VsyncDisabled">#FFFF4554</Color> - </ResourceDictionary> - <ResourceDictionary x:Key="Light"> - <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" - Color="{DynamicResource DataGridSelectionColor}" /> - <SolidColorBrush x:Key="ThemeAccentColorBrush" - Color="{DynamicResource SystemAccentColor}" /> - <SolidColorBrush x:Key="ThemeAccentBrush4" - Color="{DynamicResource ThemeAccentColor4}" /> - <Color x:Key="SystemAccentColor">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color> - <Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color> - <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> - <Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color> - <Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color> - <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> - <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> - <Color x:Key="ThemeForegroundColor">#FF000000</Color> - <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> - <Color x:Key="AppListBackgroundColor">#b3ffffff</Color> - <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> - <Color x:Key="SecondaryTextColor">#A0000000</Color> - </ResourceDictionary> - <ResourceDictionary x:Key="Dark"> - <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" - Color="{DynamicResource DataGridSelectionColor}" /> - <SolidColorBrush x:Key="ThemeAccentColorBrush" - Color="{DynamicResource SystemAccentColor}" /> - <SolidColorBrush x:Key="ThemeAccentBrush4" - Color="{DynamicResource ThemeAccentColor4}" /> - <Color x:Key="ControlFillColorSecondary">#008AA8</Color> - <Color x:Key="SystemAccentColor">#FF00C3E3</Color> - <Color x:Key="SystemAccentColorDark1">#FF99b000</Color> - <Color x:Key="SystemAccentColorDark2">#FF006d7d</Color> - <Color x:Key="SystemAccentColorDark3">#FF00525E</Color> - <Color x:Key="SystemAccentColorLight1">#FF00dbff</Color> - <Color x:Key="SystemAccentColorLight2">#FF19dfff</Color> - <Color x:Key="SystemAccentColorLight3">#FF33e3ff</Color> - <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> - <Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color> - <Color x:Key="ThemeControlBorderColor">#FF505050</Color> - <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> - <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> - <Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color> - <Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color> - <Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color> - <Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color> - <Color x:Key="SecondaryTextColor">#A0FFFFFF</Color> - </ResourceDictionary> - </ResourceDictionary.ThemeDictionaries> -</ResourceDictionary> diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx.Ava/Common/ApplicationHelper.cs deleted file mode 100644 index 622a6a02..00000000 --- a/src/Ryujinx.Ava/Common/ApplicationHelper.cs +++ /dev/null @@ -1,419 +0,0 @@ -using Avalonia.Controls.Notifications; -using Avalonia.Platform.Storage; -using Avalonia.Threading; -using LibHac; -using LibHac.Account; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Fs.Shim; -using LibHac.FsSystem; -using LibHac.Ns; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; -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 System; -using System.Buffers; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using ApplicationId = LibHac.Ncm.ApplicationId; -using Path = System.IO.Path; - -namespace Ryujinx.Ava.Common -{ - internal static class ApplicationHelper - { - private static HorizonClient _horizonClient; - private static AccountManager _accountManager; - private static VirtualFileSystem _virtualFileSystem; - - public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient) - { - _virtualFileSystem = virtualFileSystem; - _horizonClient = horizonClient; - _accountManager = accountManager; - } - - private static bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId) - { - saveDataId = default; - - Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); - if (ResultFs.TargetNotFound.Includes(result)) - { - ref ApplicationControlProperty control = ref controlHolder.Value; - - Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]"); - - if (controlHolder.ByteSpan.IsZeros()) - { - // If the current application doesn't have a loaded control property, create a dummy one - // and set the savedata sizes so a user savedata will be created. - control = ref new BlitStruct<ApplicationControlProperty>(1).Value; - - // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. - control.UserAccountSaveDataSize = 0x4000; - control.UserAccountSaveDataJournalSize = 0x4000; - - Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); - } - - Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); - - result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new ApplicationId(titleId), in control, in user); - if (result.IsFailure()) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName())); - }); - - return false; - } - - // Try to find the savedata again after creating it - result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); - } - - if (result.IsSuccess()) - { - saveDataId = saveDataInfo.SaveDataId; - - return true; - } - - Dispatcher.UIThread.InvokeAsync(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageFindSaveErrorMessage, result.ToStringWithName())); - }); - - return false; - } - - public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, BlitStruct<ApplicationControlProperty> controlData, string titleName) - { - if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId)) - { - return; - } - - OpenSaveDir(saveDataId); - } - - public static void OpenSaveDir(ulong saveDataId) - { - string saveRootPath = Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); - - if (!Directory.Exists(saveRootPath)) - { - // Inconsistent state. Create the directory - Directory.CreateDirectory(saveRootPath); - } - - string committedPath = Path.Combine(saveRootPath, "0"); - string workingPath = Path.Combine(saveRootPath, "1"); - - // If the committed directory exists, that path will be loaded the next time the savedata is mounted - if (Directory.Exists(committedPath)) - { - OpenHelper.OpenFolder(committedPath); - } - else - { - // If the working directory exists and the committed directory doesn't, - // the working directory will be loaded the next time the savedata is mounted - if (!Directory.Exists(workingPath)) - { - Directory.CreateDirectory(workingPath); - } - - OpenHelper.OpenFolder(workingPath); - } - } - - public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0) - { - var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], - AllowMultiple = false, - }); - - if (result.Count == 0) - { - return; - } - - var destination = result[0].Path.LocalPath; - var cancellationToken = new CancellationTokenSource(); - - UpdateWaitWindow waitingDialog = new( - LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)), - cancellationToken); - - Thread extractorThread = new(() => - { - Dispatcher.UIThread.Post(waitingDialog.Show); - - using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); - - Nca mainNca = null; - Nca patchNca = null; - - string extension = Path.GetExtension(titleFilePath).ToLower(); - if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") - { - IFileSystem pfs; - - if (extension == ".xci") - { - pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).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<IFile>(); - - 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) - { - 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 (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"); - - Dispatcher.UIThread.InvokeAsync(async () => - { - waitingDialog.Close(); - - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]); - }); - - return; - } - - (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); - if (updatePatchNca != null) - { - patchNca = updatePatchNca; - } - - int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType); - - try - { - bool sectionExistsInPatch = false; - if (patchNca != null) - { - sectionExistsInPatch = patchNca.CanOpenSection(index); - } - - IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) - : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); - - FileSystemClient fsClient = _horizonClient.Fs; - - string source = DateTime.Now.ToFileTime().ToString()[10..]; - string output = DateTime.Now.ToFileTime().ToString()[10..]; - - using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem); - using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination)); - - fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); - fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); - - (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token); - - if (!canceled) - { - if (resultCode.Value.IsFailure()) - { - Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); - - Dispatcher.UIThread.InvokeAsync(async () => - { - waitingDialog.Close(); - - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]); - }); - } - else if (resultCode.Value.IsSuccess()) - { - Dispatcher.UIThread.Post(waitingDialog.Close); - - NotificationHelper.Show( - LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle], - $"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}", - NotificationType.Information); - } - } - - fsClient.Unmount(source.ToU8Span()); - fsClient.Unmount(output.ToU8Span()); - } - catch (ArgumentException ex) - { - Logger.Error?.Print(LogClass.Application, $"{ex.Message}"); - - Dispatcher.UIThread.InvokeAsync(async () => - { - waitingDialog.Close(); - - await ContentDialogHelper.CreateErrorDialog(ex.Message); - }); - } - }) - { - Name = "GUI.NcaSectionExtractorThread", - IsBackground = true, - }; - extractorThread.Start(); - } - - public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token) - { - Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); - if (rc.IsFailure()) - { - return (rc, false); - } - - using (sourceHandle) - { - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) - { - if (token.IsCancellationRequested) - { - return (null, true); - } - - string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); - string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); - - if (entry.Type == DirectoryEntryType.Directory) - { - fs.EnsureDirectoryExists(subDstPath); - - (Result? result, bool canceled) = CopyDirectory(fs, subSrcPath, subDstPath, token); - if (canceled || result.Value.IsFailure()) - { - return (result, canceled); - } - } - - if (entry.Type == DirectoryEntryType.File) - { - fs.CreateOrOverwriteFile(subDstPath, entry.Size); - - rc = CopyFile(fs, subSrcPath, subDstPath); - if (rc.IsFailure()) - { - return (rc, false); - } - } - } - } - - return (Result.Success, false); - } - - public static Result CopyFile(FileSystemClient fs, string sourcePath, string destPath) - { - Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); - if (rc.IsFailure()) - { - return rc; - } - - using (sourceHandle) - { - rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend); - if (rc.IsFailure()) - { - return rc; - } - - using (destHandle) - { - const int MaxBufferSize = 1024 * 1024; - - rc = fs.GetFileSize(out long fileSize, sourceHandle); - if (rc.IsFailure()) - { - return rc; - } - - int bufferSize = (int)Math.Min(MaxBufferSize, fileSize); - - byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); - try - { - for (long offset = 0; offset < fileSize; offset += bufferSize) - { - int toRead = (int)Math.Min(fileSize - offset, bufferSize); - Span<byte> buf = buffer.AsSpan(0, toRead); - - rc = fs.ReadFile(out long _, sourceHandle, offset, buf); - if (rc.IsFailure()) - { - return rc; - } - - rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); - if (rc.IsFailure()) - { - return rc; - } - } - } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - - rc = fs.FlushFile(destHandle); - if (rc.IsFailure()) - { - return rc; - } - } - } - - return Result.Success; - } - } -} diff --git a/src/Ryujinx.Ava/Common/ApplicationSort.cs b/src/Ryujinx.Ava/Common/ApplicationSort.cs deleted file mode 100644 index 4b80e3d2..00000000 --- a/src/Ryujinx.Ava/Common/ApplicationSort.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Ava.Common -{ - internal enum ApplicationSort - { - Title, - TitleId, - Developer, - LastPlayed, - TotalTimePlayed, - FileType, - FileSize, - Path, - Favorite, - } -} diff --git a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs b/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs deleted file mode 100644 index 6e492098..00000000 --- a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.Ava.Common -{ - public enum KeyboardHotkeyState - { - None, - ToggleVSync, - Screenshot, - ShowUI, - Pause, - ToggleMute, - ResScaleUp, - ResScaleDown, - VolumeUp, - VolumeDown, - } -} diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs b/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs deleted file mode 100644 index 40661bf3..00000000 --- a/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Avalonia.Data.Core; -using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.MarkupExtensions; -using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; -using System; - -namespace Ryujinx.Ava.Common.Locale -{ - internal class LocaleExtension : MarkupExtension - { - public LocaleExtension(LocaleKeys key) - { - Key = key; - } - - public LocaleKeys Key { get; } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - LocaleKeys keyToUse = Key; - - var builder = new CompiledBindingPathBuilder(); - - builder.SetRawSource(LocaleManager.Instance) - .Property(new ClrPropertyInfo("Item", - obj => (LocaleManager.Instance[keyToUse]), - null, - typeof(string)), (weakRef, iPropInfo) => - { - return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo); - }); - - var path = builder.Build(); - - var binding = new CompiledBindingExtension(path); - - return binding.ProvideValue(serviceProvider); - } - } -} diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs deleted file mode 100644 index b2f3e7ab..00000000 --- a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs +++ /dev/null @@ -1,160 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Configuration; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; - -namespace Ryujinx.Ava.Common.Locale -{ - class LocaleManager : BaseModel - { - private const string DefaultLanguageCode = "en_US"; - - private readonly Dictionary<LocaleKeys, string> _localeStrings; - private Dictionary<LocaleKeys, string> _localeDefaultStrings; - private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues; - private string _localeLanguageCode; - - public static LocaleManager Instance { get; } = new(); - public event Action LocaleChanged; - - public LocaleManager() - { - _localeStrings = new Dictionary<LocaleKeys, string>(); - _localeDefaultStrings = new Dictionary<LocaleKeys, string>(); - _dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>(); - - Load(); - } - - public void Load() - { - // Load the system Language Code. - string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_'); - - // 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)) - { - localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value; - } - else - { - localeLanguageCode = DefaultLanguageCode; - } - } - - // Load en_US as default, if the target language translation is incomplete. - LoadDefaultLanguage(); - - LoadLanguage(localeLanguageCode); - } - - public string this[LocaleKeys key] - { - get - { - // Check if the locale contains the key. - if (_localeStrings.TryGetValue(key, out string value)) - { - // Check if the localized string needs to be formatted. - if (_dynamicValues.TryGetValue(key, out var dynamicValue)) - { - try - { - return string.Format(value, dynamicValue); - } - catch (Exception) - { - // If formatting failed use the default text instead. - if (_localeDefaultStrings.TryGetValue(key, out value)) - { - try - { - return string.Format(value, dynamicValue); - } - catch (Exception) - { - // If formatting the default text failed return the key. - return key.ToString(); - } - } - } - } - - return value; - } - - // If the locale doesn't contain the key return the default one. - if (_localeDefaultStrings.TryGetValue(key, out string defaultValue)) - { - return defaultValue; - } - - // If the locale text doesn't exist return the key. - return key.ToString(); - } - set - { - _localeStrings[key] = value; - - OnPropertyChanged(); - } - } - - public bool IsRTL() - { - return _localeLanguageCode switch - { - "he_IL" => true, - _ => false - }; - } - - public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values) - { - _dynamicValues[key] = values; - - OnPropertyChanged("Item"); - - return this[key]; - } - - private void LoadDefaultLanguage() - { - _localeDefaultStrings = LoadJsonLanguage(); - } - - public void LoadLanguage(string languageCode) - { - foreach (var item in LoadJsonLanguage(languageCode)) - { - this[item.Key] = item.Value; - } - - _localeLanguageCode = languageCode; - LocaleChanged?.Invoke(); - } - - private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode = DefaultLanguageCode) - { - var localeStrings = new Dictionary<LocaleKeys, string>(); - string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); - var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); - - foreach (var item in strings) - { - if (Enum.TryParse<LocaleKeys>(item.Key, out var key)) - { - localeStrings[key] = item.Value; - } - } - - return localeStrings; - } - } -} diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs b/src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs deleted file mode 100644 index fbaaaaba..00000000 --- a/src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs +++ /dev/null @@ -1,203 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Input; -using System; -using System.Collections.Generic; -using System.Numerics; -using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; -using Key = Ryujinx.Input.Key; - -namespace Ryujinx.Ava.Input -{ - internal class AvaloniaKeyboard : IKeyboard - { - private readonly List<ButtonMappingEntry> _buttonsUserMapping; - private readonly AvaloniaKeyboardDriver _driver; - private StandardKeyboardInputConfig _configuration; - - private readonly object _userMappingLock = new(); - - public string Id { get; } - public string Name { get; } - - public bool IsConnected => true; - public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; - - private class ButtonMappingEntry - { - public readonly Key From; - public readonly GamepadButtonInputId To; - - public ButtonMappingEntry(GamepadButtonInputId to, Key from) - { - To = to; - From = from; - } - } - - public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name) - { - _buttonsUserMapping = new List<ButtonMappingEntry>(); - - _driver = driver; - Id = id; - Name = name; - } - - public KeyboardStateSnapshot GetKeyboardStateSnapshot() - { - return IKeyboard.GetStateSnapshot(this); - } - - public GamepadStateSnapshot GetMappedStateSnapshot() - { - KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); - GamepadStateSnapshot result = default; - - lock (_userMappingLock) - { - if (_configuration == null) - { - return result; - } - - foreach (ButtonMappingEntry entry in _buttonsUserMapping) - { - if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) - { - continue; - } - - // NOTE: Do not touch state of the button already pressed. - if (!result.IsPressed(entry.To)) - { - result.SetPressed(entry.To, rawState.IsPressed(entry.From)); - } - } - - (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); - (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); - - result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); - result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); - } - - return result; - } - - public GamepadStateSnapshot GetStateSnapshot() - { - throw new NotSupportedException(); - } - - public (float, float) GetStick(StickInputId inputId) - { - throw new NotSupportedException(); - } - - public bool IsPressed(GamepadButtonInputId inputId) - { - throw new NotSupportedException(); - } - - public bool IsPressed(Key key) - { - try - { - return _driver.IsPressed(key); - } - catch - { - return false; - } - } - - public void SetConfiguration(InputConfig configuration) - { - lock (_userMappingLock) - { - _configuration = (StandardKeyboardInputConfig)configuration; - - _buttonsUserMapping.Clear(); - -#pragma warning disable IDE0055 // Disable formatting - // Left JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); - - // Right JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); -#pragma warning restore IDE0055 - } - } - - public void SetTriggerThreshold(float triggerThreshold) { } - - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { } - - public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero; - - private static float ConvertRawStickValue(short value) - { - const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); - - return value * ConvertRate; - } - - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig) - { - short stickX = 0; - short stickY = 0; - - if (snapshot.IsPressed((Key)stickConfig.StickUp)) - { - stickY += 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickDown)) - { - stickY -= 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickRight)) - { - stickX += 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickLeft)) - { - stickX -= 1; - } - - Vector2 stick = new(stickX, stickY); - - stick = Vector2.Normalize(stick); - - return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); - } - - public void Clear() - { - _driver?.ResetKeys(); - } - - public void Dispose() { } - } -} diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs deleted file mode 100644 index e9e71b99..00000000 --- a/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Input; -using System; -using System.Collections.Generic; -using AvaKey = Avalonia.Input.Key; -using Key = Ryujinx.Input.Key; - -namespace Ryujinx.Ava.Input -{ - internal class AvaloniaKeyboardDriver : IGamepadDriver - { - private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; - private readonly Control _control; - private readonly HashSet<AvaKey> _pressedKeys; - - public event EventHandler<KeyEventArgs> KeyPressed; - public event EventHandler<KeyEventArgs> KeyRelease; - public event EventHandler<string> TextInput; - - public string DriverName => "AvaloniaKeyboardDriver"; - public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; - - public AvaloniaKeyboardDriver(Control control) - { - _control = control; - _pressedKeys = new HashSet<AvaKey>(); - - _control.KeyDown += OnKeyPress; - _control.KeyUp += OnKeyRelease; - _control.TextInput += Control_TextInput; - } - - private void Control_TextInput(object sender, TextInputEventArgs e) - { - TextInput?.Invoke(this, e.Text); - } - - public event Action<string> OnGamepadConnected - { - add { } - remove { } - } - - public event Action<string> OnGamepadDisconnected - { - add { } - remove { } - } - - public IGamepad GetGamepad(string id) - { - if (!_keyboardIdentifers[0].Equals(id)) - { - return null; - } - - return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _control.KeyUp -= OnKeyPress; - _control.KeyDown -= OnKeyRelease; - } - } - - protected void OnKeyPress(object sender, KeyEventArgs args) - { - _pressedKeys.Add(args.Key); - - KeyPressed?.Invoke(this, args); - } - - protected void OnKeyRelease(object sender, KeyEventArgs args) - { - _pressedKeys.Remove(args.Key); - - KeyRelease?.Invoke(this, args); - } - - internal bool IsPressed(Key key) - { - if (key == Key.Unbound || key == Key.Unknown) - { - return false; - } - - AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey); - - return _pressedKeys.Contains(nativeKey); - } - - public void ResetKeys() - { - _pressedKeys.Clear(); - } - - public void Dispose() - { - Dispose(true); - } - } -} diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs b/src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs deleted file mode 100644 index 97ebd721..00000000 --- a/src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs +++ /dev/null @@ -1,185 +0,0 @@ -using Ryujinx.Input; -using System; -using System.Collections.Generic; -using AvaKey = Avalonia.Input.Key; - -namespace Ryujinx.Ava.Input -{ - internal static class AvaloniaKeyboardMappingHelper - { - private static readonly AvaKey[] _keyMapping = { - // NOTE: Invalid - AvaKey.None, - - AvaKey.LeftShift, - AvaKey.RightShift, - AvaKey.LeftCtrl, - AvaKey.RightCtrl, - AvaKey.LeftAlt, - AvaKey.RightAlt, - AvaKey.LWin, - AvaKey.RWin, - AvaKey.Apps, - AvaKey.F1, - AvaKey.F2, - AvaKey.F3, - AvaKey.F4, - AvaKey.F5, - AvaKey.F6, - AvaKey.F7, - AvaKey.F8, - AvaKey.F9, - AvaKey.F10, - AvaKey.F11, - AvaKey.F12, - AvaKey.F13, - AvaKey.F14, - AvaKey.F15, - AvaKey.F16, - AvaKey.F17, - AvaKey.F18, - AvaKey.F19, - AvaKey.F20, - AvaKey.F21, - AvaKey.F22, - AvaKey.F23, - AvaKey.F24, - - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - AvaKey.None, - - AvaKey.Up, - AvaKey.Down, - AvaKey.Left, - AvaKey.Right, - AvaKey.Return, - AvaKey.Escape, - AvaKey.Space, - AvaKey.Tab, - AvaKey.Back, - AvaKey.Insert, - AvaKey.Delete, - AvaKey.PageUp, - AvaKey.PageDown, - AvaKey.Home, - AvaKey.End, - AvaKey.CapsLock, - AvaKey.Scroll, - AvaKey.Print, - AvaKey.Pause, - AvaKey.NumLock, - AvaKey.Clear, - AvaKey.NumPad0, - AvaKey.NumPad1, - AvaKey.NumPad2, - AvaKey.NumPad3, - AvaKey.NumPad4, - AvaKey.NumPad5, - AvaKey.NumPad6, - AvaKey.NumPad7, - AvaKey.NumPad8, - AvaKey.NumPad9, - AvaKey.Divide, - AvaKey.Multiply, - AvaKey.Subtract, - AvaKey.Add, - AvaKey.Decimal, - AvaKey.Enter, - AvaKey.A, - AvaKey.B, - AvaKey.C, - AvaKey.D, - AvaKey.E, - AvaKey.F, - AvaKey.G, - AvaKey.H, - AvaKey.I, - AvaKey.J, - AvaKey.K, - AvaKey.L, - AvaKey.M, - AvaKey.N, - AvaKey.O, - AvaKey.P, - AvaKey.Q, - AvaKey.R, - AvaKey.S, - AvaKey.T, - AvaKey.U, - AvaKey.V, - AvaKey.W, - AvaKey.X, - AvaKey.Y, - AvaKey.Z, - AvaKey.D0, - AvaKey.D1, - AvaKey.D2, - AvaKey.D3, - AvaKey.D4, - AvaKey.D5, - AvaKey.D6, - AvaKey.D7, - AvaKey.D8, - AvaKey.D9, - AvaKey.OemTilde, - AvaKey.OemTilde,AvaKey.OemMinus, - AvaKey.OemPlus, - AvaKey.OemOpenBrackets, - AvaKey.OemCloseBrackets, - AvaKey.OemSemicolon, - AvaKey.OemQuotes, - AvaKey.OemComma, - AvaKey.OemPeriod, - AvaKey.OemQuestion, - AvaKey.OemBackslash, - - // NOTE: invalid - AvaKey.None, - }; - - private static readonly Dictionary<AvaKey, Key> _avaKeyMapping; - - static AvaloniaKeyboardMappingHelper() - { - var inputKeys = Enum.GetValues<Key>(); - - // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array. - _avaKeyMapping = new Dictionary<AvaKey, Key>(); - - foreach (var key in inputKeys) - { - if (TryGetAvaKey(key, out var index)) - { - _avaKeyMapping[index] = key; - } - } - } - - public static bool TryGetAvaKey(Key key, out AvaKey avaKey) - { - avaKey = AvaKey.None; - - bool keyExist = (int)key < _keyMapping.Length; - if (keyExist) - { - avaKey = _keyMapping[(int)key]; - } - - return keyExist; - } - - public static Key ToInputKey(AvaKey key) - { - return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); - } - } -} diff --git a/src/Ryujinx.Ava/Input/AvaloniaMouse.cs b/src/Ryujinx.Ava/Input/AvaloniaMouse.cs deleted file mode 100644 index 1aa2d586..00000000 --- a/src/Ryujinx.Ava/Input/AvaloniaMouse.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Input; -using System; -using System.Drawing; -using System.Numerics; - -namespace Ryujinx.Ava.Input -{ - internal class AvaloniaMouse : IMouse - { - private AvaloniaMouseDriver _driver; - - public string Id => "0"; - public string Name => "AvaloniaMouse"; - - public bool IsConnected => true; - public GamepadFeaturesFlag Features => throw new NotImplementedException(); - public bool[] Buttons => _driver.PressedButtons; - - public AvaloniaMouse(AvaloniaMouseDriver driver) - { - _driver = driver; - } - - public Size ClientSize => _driver.GetClientSize(); - - public Vector2 GetPosition() - { - return _driver.CurrentPosition; - } - - public Vector2 GetScroll() - { - return _driver.Scroll; - } - - public GamepadStateSnapshot GetMappedStateSnapshot() - { - throw new NotImplementedException(); - } - - public Vector3 GetMotionData(MotionInputId inputId) - { - throw new NotImplementedException(); - } - - public GamepadStateSnapshot GetStateSnapshot() - { - throw new NotImplementedException(); - } - - public (float, float) GetStick(StickInputId inputId) - { - throw new NotImplementedException(); - } - - public bool IsButtonPressed(MouseButton button) - { - return _driver.IsButtonPressed(button); - } - - public bool IsPressed(GamepadButtonInputId inputId) - { - throw new NotImplementedException(); - } - - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) - { - throw new NotImplementedException(); - } - - public void SetConfiguration(InputConfig configuration) - { - throw new NotImplementedException(); - } - - public void SetTriggerThreshold(float triggerThreshold) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - _driver = null; - } - } -} diff --git a/src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs b/src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs deleted file mode 100644 index e71bbf64..00000000 --- a/src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs +++ /dev/null @@ -1,159 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Ryujinx.Input; -using System; -using System.Numerics; -using MouseButton = Ryujinx.Input.MouseButton; -using Size = System.Drawing.Size; - -namespace Ryujinx.Ava.Input -{ - internal class AvaloniaMouseDriver : IGamepadDriver - { - private Control _widget; - private bool _isDisposed; - private Size _size; - private readonly TopLevel _window; - - public bool[] PressedButtons { get; } - public Vector2 CurrentPosition { get; private set; } - public Vector2 Scroll { get; private set; } - - public string DriverName => "AvaloniaMouseDriver"; - public ReadOnlySpan<string> GamepadsIds => new[] { "0" }; - - public AvaloniaMouseDriver(TopLevel window, Control parent) - { - _widget = parent; - _window = window; - - _widget.PointerMoved += Parent_PointerMovedEvent; - _widget.PointerPressed += Parent_PointerPressedEvent; - _widget.PointerReleased += Parent_PointerReleasedEvent; - _widget.PointerWheelChanged += Parent_PointerWheelChanged; - - _window.PointerMoved += Parent_PointerMovedEvent; - _window.PointerPressed += Parent_PointerPressedEvent; - _window.PointerReleased += Parent_PointerReleasedEvent; - _window.PointerWheelChanged += Parent_PointerWheelChanged; - - PressedButtons = new bool[(int)MouseButton.Count]; - - _size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height); - - parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized); - } - - public event Action<string> OnGamepadConnected - { - add { } - remove { } - } - - public event Action<string> OnGamepadDisconnected - { - add { } - remove { } - } - - private void Resized(Rect rect) - { - _size = new Size((int)rect.Width, (int)rect.Height); - } - - private void Parent_PointerWheelChanged(object o, PointerWheelEventArgs args) - { - Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y); - } - - private void Parent_PointerReleasedEvent(object o, PointerReleasedEventArgs args) - { - uint button = (uint)args.InitialPressMouseButton - 1; - - if ((uint)PressedButtons.Length > button) - { - PressedButtons[button] = false; - } - } - private void Parent_PointerPressedEvent(object o, PointerPressedEventArgs args) - { - uint button = (uint)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind; - - if ((uint)PressedButtons.Length > button) - { - PressedButtons[button] = true; - } - } - - private void Parent_PointerMovedEvent(object o, PointerEventArgs args) - { - Point position = args.GetPosition(_widget); - - CurrentPosition = new Vector2((float)position.X, (float)position.Y); - } - - public void SetMousePressed(MouseButton button) - { - if ((uint)PressedButtons.Length > (uint)button) - { - PressedButtons[(uint)button] = true; - } - } - - public void SetMouseReleased(MouseButton button) - { - if ((uint)PressedButtons.Length > (uint)button) - { - PressedButtons[(uint)button] = false; - } - } - - public void SetPosition(double x, double y) - { - CurrentPosition = new Vector2((float)x, (float)y); - } - - public bool IsButtonPressed(MouseButton button) - { - if ((uint)PressedButtons.Length > (uint)button) - { - return PressedButtons[(uint)button]; - } - - return false; - } - - public Size GetClientSize() - { - return _size; - } - - public IGamepad GetGamepad(string id) - { - return new AvaloniaMouse(this); - } - - public void Dispose() - { - if (_isDisposed) - { - return; - } - - _isDisposed = true; - - _widget.PointerMoved -= Parent_PointerMovedEvent; - _widget.PointerPressed -= Parent_PointerPressedEvent; - _widget.PointerReleased -= Parent_PointerReleasedEvent; - _widget.PointerWheelChanged -= Parent_PointerWheelChanged; - - _window.PointerMoved -= Parent_PointerMovedEvent; - _window.PointerPressed -= Parent_PointerPressedEvent; - _window.PointerReleased -= Parent_PointerReleasedEvent; - _window.PointerWheelChanged -= Parent_PointerWheelChanged; - - _widget = null; - } - } -} diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs deleted file mode 100644 index 5795f34f..00000000 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ /dev/null @@ -1,764 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using ICSharpCode.SharpZipLib.GZip; -using ICSharpCode.SharpZipLib.Tar; -using ICSharpCode.SharpZipLib.Zip; -using Ryujinx.Ava; -using Ryujinx.Ava.Common.Locale; -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 System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.NetworkInformation; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Ryujinx.Modules -{ - internal static class Updater - { - private const string GitHubApiUrl = "https://api.github.com"; - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; - private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); - private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); - private const int ConnectionCount = 4; - - private static string _buildVer; - private static string _platformExt; - private static string _buildUrl; - private static long _buildSize; - private static bool _updateSuccessful; - private static bool _running; - - private static readonly string[] _windowsDependencyDirs = Array.Empty<string>(); - - public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) - { - if (_running) - { - return; - } - - _running = true; - - // Detect current platform - if (OperatingSystem.IsMacOS()) - { - _platformExt = "macos_universal.app.tar.gz"; - } - else if (OperatingSystem.IsWindows()) - { - _platformExt = "win_x64.zip"; - } - else if (OperatingSystem.IsLinux()) - { - var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; - _platformExt = $"linux_{arch}.tar.gz"; - } - - Version newVersion; - Version currentVersion; - - try - { - currentVersion = Version.Parse(Program.Version); - } - catch - { - Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; - - return; - } - - // Get latest version number from GitHub API - try - { - using HttpClient jsonClient = ConstructHttpClient(); - - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); - var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); - _buildVer = fetched.Name; - - foreach (var asset in fetched.Assets) - { - if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt)) - { - _buildUrl = asset.BrowserDownloadUrl; - - if (asset.State != "uploaded") - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; - } - - break; - } - } - - // If build not done, assume no new update are available. - if (_buildUrl is null) - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; - } - } - catch (Exception exception) - { - Logger.Error?.Print(LogClass.Application, exception.Message); - - await ContentDialogHelper.CreateErrorDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - - _running = false; - - return; - } - - try - { - newVersion = Version.Parse(_buildVer); - } - catch - { - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; - - return; - } - - if (newVersion <= currentVersion) - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; - } - - // Fetch build size information to learn chunk sizes. - using HttpClient buildSizeClient = ConstructHttpClient(); - try - { - buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); - - HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); - - _buildSize = message.Content.Headers.ContentRange.Length.Value; - } - catch (Exception ex) - { - Logger.Warning?.Print(LogClass.Application, ex.Message); - Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater"); - - _buildSize = -1; - } - - await Dispatcher.UIThread.InvokeAsync(async () => - { - // Show a message asking the user if they want to update - var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - $"{Program.Version} -> {newVersion}"); - - if (shouldUpdate) - { - await UpdateRyujinx(mainWindow, _buildUrl); - } - else - { - _running = false; - } - }); - } - - private static HttpClient ConstructHttpClient() - { - HttpClient result = new(); - - // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); - - return result; - } - - private static async Task UpdateRyujinx(Window parent, string downloadUrl) - { - _updateSuccessful = false; - - // Empty update dir, although it shouldn't ever have anything inside it - if (Directory.Exists(_updateDir)) - { - Directory.Delete(_updateDir, true); - } - - Directory.CreateDirectory(_updateDir); - - string updateFile = Path.Combine(_updateDir, "update.bin"); - - TaskDialog taskDialog = new() - { - Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], - IconSource = new SymbolIconSource { Symbol = Symbol.Download }, - ShowProgressBar = true, - XamlRoot = parent, - }; - - taskDialog.Opened += (s, e) => - { - if (_buildSize >= 0) - { - DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); - } - else - { - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } - }; - - await taskDialog.ShowAsync(true); - - if (_updateSuccessful) - { - bool shouldRestart = true; - - if (!OperatingSystem.IsMacOS()) - { - shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); - } - - if (shouldRestart) - { - List<string> arguments = CommandLineState.Arguments.ToList(); - string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; - - // On macOS we perform the update at relaunch. - if (OperatingSystem.IsMacOS()) - { - string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); - string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); - string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); - string currentPid = Environment.ProcessId.ToString(); - - arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); - Process.Start("/bin/bash", arguments); - } - else - { - // Find the process name. - string ryuName = Path.GetFileName(Environment.ProcessPath); - - // Some operating systems can see the renamed executable, so strip off the .ryuold if found. - if (ryuName.EndsWith(".ryuold")) - { - ryuName = ryuName[..^7]; - } - - // Fallback if the executable could not be found. - if (!Path.Exists(Path.Combine(executableDirectory, ryuName))) - { - ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava"; - } - - ProcessStartInfo processStart = new(ryuName) - { - UseShellExecute = true, - WorkingDirectory = executableDirectory, - }; - - foreach (string argument in CommandLineState.Arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - } - - Environment.Exit(0); - } - } - } - - private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - // Multi-Threaded Updater - long chunkSize = _buildSize / ConnectionCount; - long remainderChunk = _buildSize % ConnectionCount; - - int completedRequests = 0; - int totalProgressPercentage = 0; - int[] progressPercentage = new int[ConnectionCount]; - - List<byte[]> list = new(ConnectionCount); - List<WebClient> webClients = new(ConnectionCount); - - for (int i = 0; i < ConnectionCount; i++) - { - list.Add(Array.Empty<byte>()); - } - - for (int i = 0; i < ConnectionCount; i++) - { -#pragma warning disable SYSLIB0014 - // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. - using WebClient client = new(); -#pragma warning restore SYSLIB0014 - - webClients.Add(client); - - if (i == ConnectionCount - 1) - { - client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}"); - } - else - { - client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}"); - } - - client.DownloadProgressChanged += (_, args) => - { - int index = (int)args.UserState; - - Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]); - Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); - Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); - - taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); - }; - - client.DownloadDataCompleted += (_, args) => - { - int index = (int)args.UserState; - - if (args.Cancelled) - { - webClients[index].Dispose(); - - taskDialog.Hide(); - - return; - } - - list[index] = args.Result; - Interlocked.Increment(ref completedRequests); - - if (Equals(completedRequests, ConnectionCount)) - { - byte[] mergedFileBytes = new byte[_buildSize]; - for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++) - { - Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length); - destinationOffset += list[connectionIndex].Length; - } - - File.WriteAllBytes(updateFile, mergedFileBytes); - - // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. - if (OperatingSystem.IsMacOS()) - { - using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }); - - xattrProcess.WaitForExit(); - } - - try - { - InstallUpdate(taskDialog, updateFile); - } - catch (Exception e) - { - Logger.Warning?.Print(LogClass.Application, e.Message); - Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } - } - }; - - try - { - client.DownloadDataAsync(new Uri(downloadUrl), i); - } - catch (WebException ex) - { - Logger.Warning?.Print(LogClass.Application, ex.Message); - Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - - foreach (WebClient webClient in webClients) - { - webClient.CancelAsync(); - } - - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - - return; - } - } - } - - private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - using HttpClient client = new(); - // We do not want to timeout while downloading - client.Timeout = TimeSpan.FromDays(1); - - using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result; - using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result; - using Stream updateFileStream = File.Open(updateFile, FileMode.Create); - - long totalBytes = response.Content.Headers.ContentLength.Value; - long byteWritten = 0; - - byte[] buffer = new byte[32 * 1024]; - - while (true) - { - int readSize = remoteFileStream.Read(buffer); - - if (readSize == 0) - { - break; - } - - byteWritten += readSize; - - taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); - - updateFileStream.Write(buffer, 0, readSize); - } - - InstallUpdate(taskDialog, updateFile); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double GetPercentage(double value, double max) - { - return max == 0 ? 0 : value / max * 100; - } - - private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)) - { - Name = "Updater.SingleThreadWorker", - }; - - worker.Start(); - } - - [SupportedOSPlatform("linux")] - [SupportedOSPlatform("macos")] - private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) - { - using Stream inStream = File.OpenRead(archivePath); - using GZipInputStream gzipStream = new(inStream); - using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - - TarEntry tarEntry; - - while ((tarEntry = tarStream.GetNextEntry()) is not null) - { - if (tarEntry.IsDirectory) - { - continue; - } - - string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name); - - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - - using FileStream outStream = File.OpenWrite(outPath); - tarStream.CopyEntryContents(outStream); - - File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); - - Dispatcher.UIThread.Post(() => - { - if (tarEntry is null) - { - return; - } - - taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal); - }); - } - } - - private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) - { - using Stream inStream = File.OpenRead(archivePath); - using ZipFile zipFile = new(inStream); - - double count = 0; - foreach (ZipEntry zipEntry in zipFile) - { - count++; - if (zipEntry.IsDirectory) - { - continue; - } - - string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name); - - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - - using Stream zipStream = zipFile.GetInputStream(zipEntry); - using FileStream outStream = File.OpenWrite(outPath); - - zipStream.CopyTo(outStream); - - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); - - Dispatcher.UIThread.Post(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal); - }); - } - } - - private static void InstallUpdate(TaskDialog taskDialog, string updateFile) - { - // Extract Update - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - ExtractTarGzipFile(taskDialog, updateFile, _updateDir); - } - else if (OperatingSystem.IsWindows()) - { - ExtractZipFile(taskDialog, updateFile, _updateDir); - } - else - { - throw new NotSupportedException(); - } - - // Delete downloaded zip - File.Delete(updateFile); - - List<string> allFiles = EnumerateFilesToDelete().ToList(); - - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - - // NOTE: On macOS, replacement is delayed to the restart phase. - if (!OperatingSystem.IsMacOS()) - { - // Replace old files - double count = 0; - foreach (string file in allFiles) - { - count++; - try - { - File.Move(file, file + ".ryuold"); - - Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); - }); - } - catch - { - Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); - } - } - - Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - }); - - MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); - - Directory.Delete(_updateDir, true); - } - - _updateSuccessful = true; - - taskDialog.Hide(); - } - - public static bool CanUpdate(bool showWarnings) - { -#if !DISABLE_UPDATER - if (!NetworkInterface.GetIsNetworkAvailable()) - { - if (showWarnings) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) - ); - } - - return false; - } - - if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) - { - if (showWarnings) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); - } - - return false; - } - - return true; -#else - if (showWarnings) - { - if (ReleaseInformation.IsFlatHubBuild) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) - ); - } - else - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); - } - } - - return false; -#endif - } - - // NOTE: This method should always reflect the latest build layout. - private static IEnumerable<string> EnumerateFilesToDelete() - { - var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. - - // Determine and exclude user files only when the updater is running, not when cleaning old files - if (_running && !OperatingSystem.IsMacOS()) - { - // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. - var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); - var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); - var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename)); - - // Remove user files from the paths in files. - files = files.Except(userFiles); - } - - if (OperatingSystem.IsWindows()) - { - foreach (string dir in _windowsDependencyDirs) - { - string dirPath = Path.Combine(_homeDir, dir); - if (Directory.Exists(dirPath)) - { - files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories)); - } - } - } - - return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); - } - - private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) - { - int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; - foreach (string directory in Directory.GetDirectories(root)) - { - string dirName = Path.GetFileName(directory); - - if (!Directory.Exists(Path.Combine(dest, dirName))) - { - Directory.CreateDirectory(Path.Combine(dest, dirName)); - } - - MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); - } - - double count = 0; - foreach (string file in Directory.GetFiles(root)) - { - count++; - - File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); - - Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); - }); - } - } - - public static void CleanupUpdate() - { - foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) - { - File.Delete(file); - } - } - } -} diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs deleted file mode 100644 index aecc585f..00000000 --- a/src/Ryujinx.Ava/Program.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Avalonia; -using Avalonia.Threading; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.GraphicsDriver; -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 System; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Ryujinx.Ava -{ - internal partial class Program - { - public static double WindowScaleFactor { get; set; } - public static double DesktopScaleFactor { get; set; } = 1.0; - public static string Version { get; private set; } - public static string ConfigurationPath { get; private set; } - public static bool PreviewerDetached { get; private set; } - - [LibraryImport("user32.dll", SetLastError = true)] - public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - - private const uint MbIconwarning = 0x30; - - public static void Main(string[] args) - { - Version = ReleaseInformation.Version; - - if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) - { - _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); - } - - PreviewerDetached = true; - - Initialize(args); - - LoggerAdapter.Register(); - - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - } - - public static AppBuilder BuildAvaloniaApp() - { - return AppBuilder.Configure<App>() - .UsePlatformDetect() - .With(new X11PlatformOptions - { - EnableMultiTouch = true, - EnableIme = true, - EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, - }) - .With(new Win32PlatformOptions - { - WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, - }) - .UseSkia(); - } - - private static void Initialize(string[] args) - { - // Parse arguments - CommandLineState.ParseArguments(args); - - // Delete backup files after updating. - Task.Run(Updater.CleanupUpdate); - - Console.Title = $"Ryujinx Console {Version}"; - - // Hook unhandled exception and process exit events. - AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit(); - - // Setup base data directory. - AppDataManager.Initialize(CommandLineState.BaseDirPathArg); - - // Initialize the configuration. - ConfigurationState.Initialize(); - - // Initialize the logger system. - LoggerModule.Initialize(); - - // Initialize Discord integration. - DiscordIntegrationModule.Initialize(); - - // Initialize SDL2 driver - SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); - - ReloadConfig(); - - WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); - - // Logging system information. - PrintSystemInfo(); - - // Enable OGL multithreading on the driver, when available. - DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); - - // Check if keys exists. - if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) - { - if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) - { - MainWindow.ShowKeyErrorOnLoad = true; - } - } - - if (CommandLineState.LaunchPathArg != null) - { - MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); - } - } - - public static void ReloadConfig() - { - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); - string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); - - // Now load the configuration as the other subsystems are now registered - if (File.Exists(localConfigurationPath)) - { - ConfigurationPath = localConfigurationPath; - } - else if (File.Exists(appDataConfigurationPath)) - { - ConfigurationPath = appDataConfigurationPath; - } - - if (ConfigurationPath == null) - { - // No configuration, we load the default values and save it to disk - ConfigurationPath = appDataConfigurationPath; - - ConfigurationState.Instance.LoadDefault(); - ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); - } - else - { - if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) - { - ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); - } - else - { - ConfigurationState.Instance.LoadDefault(); - - Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); - } - } - - // Check if graphics backend was overridden - if (CommandLineState.OverrideGraphicsBackend != null) - { - if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") - { - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; - } - else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") - { - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan; - } - } - - // Check if docked mode was overriden. - if (CommandLineState.OverrideDockedMode.HasValue) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; - } - - // Check if HideCursor was overridden. - if (CommandLineState.OverrideHideCursor is not null) - { - ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch - { - "never" => HideCursorMode.Never, - "onidle" => HideCursorMode.OnIdle, - "always" => HideCursorMode.Always, - _ => ConfigurationState.Instance.HideCursor.Value, - }; - } - } - - private static void PrintSystemInfo() - { - Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); - SystemInfo.Gather().Print(); - - Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}"); - - if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); - } - else - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); - } - } - - private static void ProcessUnhandledException(Exception ex, bool isTerminating) - { - string message = $"Unhandled exception caught: {ex}"; - - Logger.Error?.PrintMsg(LogClass.Application, message); - - if (Logger.Error == null) - { - Logger.Notice.PrintMsg(LogClass.Application, message); - } - - if (isTerminating) - { - Exit(); - } - } - - public static void Exit() - { - DiscordIntegrationModule.Exit(); - - Logger.Shutdown(); - } - } -} diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj deleted file mode 100644 index 91c2744f..00000000 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ /dev/null @@ -1,167 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> - <OutputType>Exe</OutputType> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <Version>1.0.0-dirty</Version> - <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> - <SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate> - <RootNamespace>Ryujinx.Ava</RootNamespace> - <ApplicationIcon>Ryujinx.ico</ApplicationIcon> - <TieredPGO>true</TieredPGO> - <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> - <ApplicationManifest>app.manifest</ApplicationManifest> - </PropertyGroup> - - <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))"> - <Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" /> - </Target> - - <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''"> - <PublishSingleFile>true</PublishSingleFile> - <TrimmerSingleWarn>false</TrimmerSingleWarn> - <PublishTrimmed>true</PublishTrimmed> - <TrimMode>partial</TrimMode> - </PropertyGroup> - - <!-- - FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json. - See: - https://github.com/amwx/FluentAvalonia/issues/481 - https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/ - --> - <PropertyGroup> - <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Avalonia" /> - <PackageReference Include="Avalonia.Desktop" /> - <PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" /> - <PackageReference Include="Avalonia.Controls.DataGrid" /> - <PackageReference Include="Avalonia.Markup.Xaml.Loader" /> - <PackageReference Include="Avalonia.Svg" /> - <PackageReference Include="Avalonia.Svg.Skia" /> - <PackageReference Include="DynamicData" /> - <PackageReference Include="FluentAvaloniaUI" /> - - <PackageReference Include="OpenTK.Core" /> - <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> - <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> - <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" /> - <PackageReference Include="Silk.NET.Vulkan" /> - <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" /> - <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> - <PackageReference Include="SPB" /> - <PackageReference Include="SharpZipLib" /> - <PackageReference Include="SixLabors.ImageSharp" /> - - <!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA --> - <PackageReference Include="System.Drawing.Common" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" /> - <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> - <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> - <ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" /> - <ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" /> - <ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" /> - <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> - <ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" /> - <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" /> - <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> - <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" /> - <ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" /> - <ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> - </ItemGroup> - - <ItemGroup> - <Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <TargetPath>alsoft.ini</TargetPath> - </Content> - <Content Include="..\..\distribution\legal\THIRDPARTY.md"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <TargetPath>THIRDPARTY.md</TargetPath> - </Content> - <Content Include="..\..\LICENSE.txt"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <TargetPath>LICENSE.txt</TargetPath> - </Content> - </ItemGroup> - - <ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'"> - <Content Include="..\..\distribution\linux\Ryujinx.sh"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> - <Content Include="..\..\distribution\linux\mime\Ryujinx.xml"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <TargetPath>mime\Ryujinx.xml</TargetPath> - </Content> - </ItemGroup> - - <ItemGroup> - <AvaloniaResource Include="UI\**\*.xaml"> - <SubType>Designer</SubType> - </AvaloniaResource> - <AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" /> - <AvaloniaResource Include="Assets\Styles\Themes.xaml"> - <Generator>MSBuild:Compile</Generator> - </AvaloniaResource> - <AvaloniaResource Include="Assets\Styles\Styles.xaml" /> - </ItemGroup> - - <ItemGroup> - <None Remove="Assets\Locales\el_GR.json" /> - <None Remove="Assets\Locales\en_US.json" /> - <None Remove="Assets\Locales\es_ES.json" /> - <None Remove="Assets\Locales\fr_FR.json" /> - <None Remove="Assets\Locales\he_IL.json" /> - <None Remove="Assets\Locales\de_DE.json" /> - <None Remove="Assets\Locales\it_IT.json" /> - <None Remove="Assets\Locales\ja_JP.json" /> - <None Remove="Assets\Locales\ko_KR.json" /> - <None Remove="Assets\Locales\pl_PL.json" /> - <None Remove="Assets\Locales\pt_BR.json" /> - <None Remove="Assets\Locales\ru_RU.json" /> - <None Remove="Assets\Locales\tr_TR.json" /> - <None Remove="Assets\Locales\uk_UA.json" /> - <None Remove="Assets\Locales\zh_CN.json" /> - <None Remove="Assets\Locales\zh_TW.json" /> - <None Remove="Assets\Styles\Styles.xaml" /> - <None Remove="Assets\Styles\Themes.xaml" /> - <None Remove="Assets\Icons\Controller_JoyConLeft.svg" /> - <None Remove="Assets\Icons\Controller_JoyConPair.svg" /> - <None Remove="Assets\Icons\Controller_JoyConRight.svg" /> - <None Remove="Assets\Icons\Controller_ProCon.svg" /> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Include="Assets\Locales\el_GR.json" /> - <EmbeddedResource Include="Assets\Locales\en_US.json" /> - <EmbeddedResource Include="Assets\Locales\es_ES.json" /> - <EmbeddedResource Include="Assets\Locales\fr_FR.json" /> - <EmbeddedResource Include="Assets\Locales\he_IL.json" /> - <EmbeddedResource Include="Assets\Locales\de_DE.json" /> - <EmbeddedResource Include="Assets\Locales\it_IT.json" /> - <EmbeddedResource Include="Assets\Locales\ja_JP.json" /> - <EmbeddedResource Include="Assets\Locales\ko_KR.json" /> - <EmbeddedResource Include="Assets\Locales\pl_PL.json" /> - <EmbeddedResource Include="Assets\Locales\pt_BR.json" /> - <EmbeddedResource Include="Assets\Locales\ru_RU.json" /> - <EmbeddedResource Include="Assets\Locales\tr_TR.json" /> - <EmbeddedResource Include="Assets\Locales\uk_UA.json" /> - <EmbeddedResource Include="Assets\Locales\zh_CN.json" /> - <EmbeddedResource Include="Assets\Locales\zh_TW.json" /> - <EmbeddedResource Include="Assets\Styles\Styles.xaml" /> - <EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" /> - <EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" /> - <EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" /> - <EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" /> - </ItemGroup> - <ItemGroup> - <AdditionalFiles Include="Assets\Locales\en_US.json" /> - </ItemGroup> -</Project> diff --git a/src/Ryujinx.Ava/Ryujinx.ico b/src/Ryujinx.Ava/Ryujinx.ico deleted file mode 100644 index edf1b93f..00000000 Binary files a/src/Ryujinx.Ava/Ryujinx.ico and /dev/null differ diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs deleted file mode 100644 index 4bcc35a7..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 deleted file mode 100644 index 531d0061..00000000 --- a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Threading; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.HLE.UI; -using System; -using System.Threading; -using HidKey = Ryujinx.Common.Configuration.Hid.Key; - -namespace Ryujinx.Ava.UI.Applet -{ - class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler - { - private MainWindow _parent; - private readonly OffscreenTextBox _hiddenTextBox; - private bool _canProcessInput; - private IDisposable _textChangedSubscription; - private IDisposable _selectionStartChangedSubscription; - private IDisposable _selectionEndtextChangedSubscription; - - public AvaloniaDynamicTextInputHandler(MainWindow parent) - { - _parent = parent; - - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed; - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease += AvaloniaDynamicTextInputHandler_KeyRelease; - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput += AvaloniaDynamicTextInputHandler_TextInput; - - _hiddenTextBox = _parent.HiddenTextBox; - - Dispatcher.UIThread.Post(() => - { - _textChangedSubscription = _hiddenTextBox.GetObservable(TextBox.TextProperty).Subscribe(TextChanged); - _selectionStartChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionStartProperty).Subscribe(SelectionChanged); - _selectionEndtextChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionEndProperty).Subscribe(SelectionChanged); - }); - } - - private void TextChanged(string text) - { - TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); - } - - private void SelectionChanged(int selection) - { - if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) - { - _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd; - } - - TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); - } - - private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) - { - Dispatcher.UIThread.InvokeAsync(() => - { - if (_canProcessInput) - { - _hiddenTextBox.SendText(text); - } - }); - } - - private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) - { - var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); - - if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) - { - return; - } - - e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent(); - - Dispatcher.UIThread.InvokeAsync(() => - { - if (_canProcessInput) - { - _hiddenTextBox.SendKeyUpEvent(e); - } - }); - } - - private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) - { - var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); - - if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) - { - return; - } - - e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent(); - - Dispatcher.UIThread.InvokeAsync(() => - { - if (_canProcessInput) - { - _hiddenTextBox.SendKeyDownEvent(e); - } - }); - } - - public bool TextProcessingEnabled - { - get - { - return Volatile.Read(ref _canProcessInput); - } - set - { - Volatile.Write(ref _canProcessInput, value); - } - } - - public event DynamicTextChangedHandler TextChangedEvent; - public event KeyPressedHandler KeyPressedEvent; - public event KeyReleasedHandler KeyReleasedEvent; - - public void Dispose() - { - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed; - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease; - (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput -= AvaloniaDynamicTextInputHandler_TextInput; - - _textChangedSubscription?.Dispose(); - _selectionStartChangedSubscription?.Dispose(); - _selectionEndtextChangedSubscription?.Dispose(); - - Dispatcher.UIThread.Post(() => - { - _hiddenTextBox.Clear(); - _parent.ViewModel.RendererHostControl.Focus(); - - _parent = null; - }); - } - - public void SetText(string text, int cursorBegin) - { - Dispatcher.UIThread.Post(() => - { - _hiddenTextBox.Text = text; - _hiddenTextBox.CaretIndex = cursorBegin; - }); - } - - public void SetText(string text, int cursorBegin, int cursorEnd) - { - Dispatcher.UIThread.Post(() => - { - _hiddenTextBox.Text = text; - _hiddenTextBox.SelectionStart = cursorBegin; - _hiddenTextBox.SelectionEnd = cursorEnd; - }); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs deleted file mode 100644 index 016fb484..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 b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml deleted file mode 100644 index b2c22f6b..00000000 --- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml +++ /dev/null @@ -1,145 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Applet.ControllerAppletDialog" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:applet="using:Ryujinx.Ava.UI.Applet" - mc:Ignorable="d" - Width="400" - Focusable="True" - x:DataType="applet:ControllerAppletDialog"> - <Grid - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <Border - Grid.Column="0" - Grid.Row="0" - Grid.ColumnSpan="2" - Margin="0 0 0 10" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5"> - <StackPanel - Spacing="10" - Margin="10"> - <TextBlock - Text="{locale:Locale ControllerAppletDescription}" /> - <TextBlock - IsVisible="{Binding IsDocked}" - FontWeight="Bold" - Text="{locale:Locale ControllerAppletDocked}" /> - </StackPanel> - </Border> - <Border - Grid.Column="0" - Grid.Row="1" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - Margin="0 0 10 0"> - <StackPanel - Margin="10" - Spacing="10" - Orientation="Vertical"> - <TextBlock - HorizontalAlignment="Center" - VerticalAlignment="Center" - TextAlignment="Center" - FontWeight="Bold" - Text="{locale:Locale ControllerAppletControllers}" /> - <StackPanel - Spacing="10" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <Image - Height="50" - Width="50" - Stretch="Uniform" - Source="{Binding ProControllerImage}" - IsVisible="{Binding SupportsProController}" /> - <Image - Height="50" - Width="50" - Stretch="Uniform" - Source="{Binding JoyconPairImage}" - IsVisible="{Binding SupportsJoyconPair}" /> - <Image - Height="50" - Width="50" - Stretch="Uniform" - Source="{Binding JoyconLeftImage}" - IsVisible="{Binding SupportsLeftJoycon}" /> - <Image - Height="50" - Width="50" - Stretch="Uniform" - Source="{Binding JoyconRightImage}" - IsVisible="{Binding SupportsRightJoycon}" /> - </StackPanel> - </StackPanel> - </Border> - <Border - Grid.Column="1" - Grid.Row="1" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5"> - <StackPanel - Margin="10" - Spacing="10" - Orientation="Vertical"> - <TextBlock - HorizontalAlignment="Center" - VerticalAlignment="Center" - TextAlignment="Center" - FontWeight="Bold" - Text="{locale:Locale ControllerAppletPlayers}" /> - <Border Height="50"> - <TextBlock - HorizontalAlignment="Center" - VerticalAlignment="Center" - TextAlignment="Center" - FontSize="40" - FontWeight="Thin" - Text="{Binding PlayerCount}" /> - </Border> - </StackPanel> - </Border> - <Panel - Margin="0 24 0 0" - Grid.Column="0" - Grid.Row="2" - Grid.ColumnSpan="2"> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Right"> - <Button - Name="SaveButton" - MinWidth="90" - Command="{Binding OpenSettingsWindow}"> - <TextBlock Text="{locale:Locale DialogOpenSettingsWindowLabel}" /> - </Button> - <Button - Name="CancelButton" - MinWidth="90" - Command="{Binding Close}"> - <TextBlock Text="{locale:Locale SettingsButtonClose}" /> - </Button> - </StackPanel> - </Panel> - </Grid> -</UserControl> - diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs deleted file mode 100644 index 279af07c..00000000 --- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Styling; -using Avalonia.Svg.Skia; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.HLE.HOS.Applets; -using Ryujinx.HLE.HOS.Services.Hid; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Applet -{ - internal partial class ControllerAppletDialog : UserControl - { - private const string ProControllerResource = "Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg"; - private const string JoyConPairResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg"; - private const string JoyConLeftResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg"; - private const string JoyConRightResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg"; - - public static SvgImage ProControllerImage => GetResource(ProControllerResource); - public static SvgImage JoyconPairImage => GetResource(JoyConPairResource); - public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource); - public static SvgImage JoyconRightImage => GetResource(JoyConRightResource); - - public string PlayerCount { get; set; } = ""; - public bool SupportsProController { get; set; } - public bool SupportsLeftJoycon { get; set; } - public bool SupportsRightJoycon { get; set; } - public bool SupportsJoyconPair { get; set; } - public bool IsDocked { get; set; } - - private readonly MainWindow _mainWindow; - - public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args) - { - if (args.PlayerCountMin == args.PlayerCountMax) - { - PlayerCount = args.PlayerCountMin.ToString(); - } - else - { - PlayerCount = $"{args.PlayerCountMin} - {args.PlayerCountMax}"; - } - - SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0; - SupportsLeftJoycon = (args.SupportedStyles & ControllerType.JoyconLeft) != 0; - SupportsRightJoycon = (args.SupportedStyles & ControllerType.JoyconRight) != 0; - SupportsJoyconPair = (args.SupportedStyles & ControllerType.JoyconPair) != 0; - - IsDocked = args.IsDocked; - - _mainWindow = mainWindow; - - DataContext = this; - - InitializeComponent(); - } - - public ControllerAppletDialog(MainWindow mainWindow) - { - _mainWindow = mainWindow; - DataContext = this; - - InitializeComponent(); - } - - public static async Task<UserResult> ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args) - { - ContentDialog contentDialog = new(); - UserResult result = UserResult.Cancel; - ControllerAppletDialog content = new(window, args); - - contentDialog.Title = LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle]; - contentDialog.Content = content; - - void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) - { - if (eventArgs.Result == ContentDialogResult.Primary) - { - result = UserResult.Ok; - } - } - - contentDialog.Closed += Handler; - - Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); - bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); - - contentDialog.Styles.Add(bottomBorder); - - await ContentDialogHelper.ShowAsync(contentDialog); - - return result; - } - - private static SvgImage GetResource(string path) - { - SvgImage image = new(); - - if (!string.IsNullOrWhiteSpace(path)) - { - SvgSource source = new(default(Uri)); - - source.Load(EmbeddedResources.GetStream(path)); - - image.Source = source; - } - - return image; - } - - public void OpenSettingsWindow() - { - if (_mainWindow.SettingsWindow == null) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - _mainWindow.SettingsWindow = new SettingsWindow(_mainWindow.VirtualFileSystem, _mainWindow.ContentManager); - _mainWindow.SettingsWindow.NavPanel.Content = _mainWindow.SettingsWindow.InputPage; - _mainWindow.SettingsWindow.NavPanel.SelectedItem = _mainWindow.SettingsWindow.NavPanel.MenuItems.ElementAt(1); - - await ContentDialogHelper.ShowWindowAsync(_mainWindow.SettingsWindow, _mainWindow); - _mainWindow.SettingsWindow = null; - this.Close(); - }); - } - } - - public void Close() - { - ((ContentDialog)Parent)?.Hide(); - } - } -} - diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml deleted file mode 100644 index 51f37051..00000000 --- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml +++ /dev/null @@ -1,54 +0,0 @@ -<Window - x:Class="Ryujinx.Ava.UI.Applet.ErrorAppletWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="{locale:Locale ErrorWindowTitle}" - xmlns:views="using:Ryujinx.Ava.UI.Applet" - Width="450" - Height="340" - CanResize="False" - x:DataType="views:ErrorAppletWindow" - SizeToContent="Height" - mc:Ignorable="d" - Focusable="True"> - <Grid - Margin="20" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Image - Grid.Row="1" - Grid.RowSpan="2" - Grid.Column="0" - Height="80" - MinWidth="50" - Margin="5,10,20,10" - Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> - <TextBlock - Grid.Row="1" - Grid.Column="1" - Margin="10" - VerticalAlignment="Stretch" - Text="{Binding Message}" - TextWrapping="Wrap" /> - <StackPanel - Name="ButtonStack" - Grid.Row="2" - Grid.Column="1" - Margin="10" - HorizontalAlignment="Right" - Orientation="Horizontal" - Spacing="10" /> - </Grid> -</Window> diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs deleted file mode 100644 index ec6f7682..00000000 --- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Windows; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Applet -{ - internal partial class ErrorAppletWindow : StyleableWindow - { - private readonly Window _owner; - private object _buttonResponse; - - public ErrorAppletWindow(Window owner, string[] buttons, string message) - { - _owner = owner; - Message = message; - DataContext = this; - InitializeComponent(); - - int responseId = 0; - - if (buttons != null) - { - foreach (string buttonText in buttons) - { - AddButton(buttonText, responseId); - responseId++; - } - } - else - { - AddButton(LocaleManager.Instance[LocaleKeys.InputDialogOk], 0); - } - } - - public ErrorAppletWindow() - { - DataContext = this; - InitializeComponent(); - } - - public string Message { get; set; } - - private void AddButton(string label, object tag) - { - Dispatcher.UIThread.InvokeAsync(() => - { - Button button = new() { Content = label, Tag = tag }; - - button.Click += Button_Click; - ButtonStack.Children.Add(button); - }); - } - - private void Button_Click(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - _buttonResponse = button.Tag; - } - - Close(); - } - - public async Task<object> Run() - { - await ShowDialog(_owner); - - return _buttonResponse; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml deleted file mode 100644 index b1c84734..00000000 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml +++ /dev/null @@ -1,67 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Controls.SwkbdAppletDialog" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:views="using:Ryujinx.Ava.UI.Controls" - Width="400" - x:DataType="views:SwkbdAppletDialog" - mc:Ignorable="d" - Focusable="True"> - <Grid - Margin="20" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Image - Grid.Row="1" - Grid.RowSpan="5" - Height="80" - MinWidth="50" - Margin="5,10,20,10" - VerticalAlignment="Center" - Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> - <TextBlock - Grid.Row="1" - Grid.Column="1" - Margin="5" - Text="{Binding MainText}" - TextWrapping="Wrap" /> - <TextBlock - Grid.Row="2" - Grid.Column="1" - Margin="5" - Text="{Binding SecondaryText}" - TextWrapping="Wrap" /> - <TextBox - Name="Input" - Grid.Row="3" - Grid.Column="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - Focusable="True" - KeyUp="Message_KeyUp" - Text="{Binding Message}" - TextInput="Message_TextInput" - TextWrapping="Wrap" - UseFloatingWatermark="True" /> - <TextBlock - Name="Error" - Grid.Row="4" - Grid.Column="1" - Margin="5" - HorizontalAlignment="Stretch" - TextWrapping="Wrap" /> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs deleted file mode 100644 index af3837e4..00000000 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs +++ /dev/null @@ -1,183 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Media; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.HLE.HOS.Applets; -using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Controls -{ - internal partial class SwkbdAppletDialog : UserControl - { - private Predicate<int> _checkLength = _ => true; - private Predicate<string> _checkInput = _ => true; - private int _inputMax; - private int _inputMin; - private readonly string _placeholder; - - private ContentDialog _host; - - public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message) - { - MainText = mainText; - SecondaryText = secondaryText; - Message = message ?? ""; - DataContext = this; - _placeholder = placeholder; - InitializeComponent(); - - Input.Watermark = _placeholder; - - Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); - } - - public SwkbdAppletDialog() - { - DataContext = this; - InitializeComponent(); - } - - protected override void OnGotFocus(GotFocusEventArgs e) - { - // FIXME: This does not work. Might be a bug in Avalonia with DialogHost - // Currently focus will be redirected to the overlay window instead. - Input.Focus(); - } - - public string Message { get; set; } = ""; - public string MainText { get; set; } = ""; - public string SecondaryText { get; set; } = ""; - - public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args) - { - ContentDialog contentDialog = new(); - - UserResult result = UserResult.Cancel; - - SwkbdAppletDialog content = new(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText); - - string input = string.Empty; - - content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); - content.SetInputValidation(args.KeyboardMode); - - content._host = contentDialog; - contentDialog.Title = title; - contentDialog.PrimaryButtonText = args.SubmitText; - contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); - contentDialog.SecondaryButtonText = ""; - contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel]; - contentDialog.Content = content; - - void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) - { - if (eventArgs.Result == ContentDialogResult.Primary) - { - result = UserResult.Ok; - input = content.Input.Text; - } - } - - contentDialog.Closed += Handler; - - await ContentDialogHelper.ShowAsync(contentDialog); - - return (result, input); - } - - private void ApplyValidationInfo(string text) - { - Error.IsVisible = !string.IsNullOrEmpty(text); - Error.Text = text; - } - - public void SetInputLengthValidation(int min, int max) - { - _inputMin = Math.Min(min, max); - _inputMax = Math.Max(min, max); - - Error.IsVisible = false; - Error.FontStyle = FontStyle.Italic; - - string validationInfoText = ""; - - if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. - { - Error.IsVisible = false; - - _checkLength = length => true; - } - else if (_inputMin > 0 && _inputMax == int.MaxValue) - { - validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin); - - _checkLength = length => _inputMin <= length; - } - else - { - validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax); - - _checkLength = length => _inputMin <= length && length <= _inputMax; - } - - ApplyValidationInfo(validationInfoText); - Message_TextInput(this, new TextInputEventArgs()); - } - - private void SetInputValidation(KeyboardMode mode) - { - string validationInfoText = Error.Text; - string localeText; - switch (mode) - { - case KeyboardMode.Numeric: - localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric); - validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); - _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); - break; - case KeyboardMode.Alphabet: - localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet); - validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); - _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); - break; - case KeyboardMode.ASCII: - localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII); - validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); - _checkInput = text => text.All(char.IsAscii); - break; - default: - _checkInput = _ => true; - break; - } - - ApplyValidationInfo(validationInfoText); - Message_TextInput(this, new TextInputEventArgs()); - } - - private void Message_TextInput(object sender, TextInputEventArgs e) - { - if (_host != null) - { - _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); - } - } - - private void Message_KeyUp(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter && _host.IsPrimaryButtonEnabled) - { - _host.Hide(ContentDialogResult.Primary); - } - else - { - _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml deleted file mode 100644 index dd0926fc..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml +++ /dev/null @@ -1,95 +0,0 @@ -<MenuFlyout - x:Class="Ryujinx.Ava.UI.Controls.ApplicationContextMenu" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - x:DataType="viewModels:MainWindowViewModel"> - <MenuItem - Click="RunApplication_Click" - Header="{locale:Locale GameListContextMenuRunApplication}" /> - <MenuItem - Click="ToggleFavorite_Click" - Header="{locale:Locale GameListContextMenuToggleFavorite}" - ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> - <MenuItem - Click="CreateApplicationShortcut_Click" - Header="{locale:Locale GameListContextMenuCreateShortcut}" - IsEnabled="{Binding CreateShortcutEnabled}" - ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> - <Separator /> - <MenuItem - Click="OpenUserSaveDirectory_Click" - Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" - IsEnabled="{Binding OpenUserSaveDirectoryEnabled}" - ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> - <MenuItem - Click="OpenDeviceSaveDirectory_Click" - Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}" - IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}" - ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" /> - <MenuItem - Click="OpenBcatSaveDirectory_Click" - Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}" - IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}" - ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" /> - <Separator /> - <MenuItem - Click="OpenTitleUpdateManager_Click" - Header="{locale:Locale GameListContextMenuManageTitleUpdates}" - ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> - <MenuItem - Click="OpenDownloadableContentManager_Click" - Header="{locale:Locale GameListContextMenuManageDlc}" - ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> - <MenuItem - Click="OpenCheatManager_Click" - Header="{locale:Locale GameListContextMenuManageCheat}" - ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> - <MenuItem - Click="OpenModManager_Click" - Header="{locale:Locale GameListContextMenuManageMod}" - ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" /> - <Separator /> - <MenuItem - Click="OpenModsDirectory_Click" - Header="{locale:Locale GameListContextMenuOpenModsDirectory}" - ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> - <MenuItem - Click="OpenSdModsDirectory_Click" - Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" - ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> - <Separator /> - <MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}"> - <MenuItem - Click="PurgePtcCache_Click" - Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" - ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> - <MenuItem - Click="PurgeShaderCache_Click" - Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" - ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> - <MenuItem - Click="OpenPtcDirectory_Click" - Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" - ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> - <MenuItem - Click="OpenShaderCacheDirectory_Click" - Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" - ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> - </MenuItem> - <MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> - <MenuItem - Click="ExtractApplicationExeFs_Click" - Header="{locale:Locale GameListContextMenuExtractDataExeFS}" - ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" /> - <MenuItem - Click="ExtractApplicationRomFs_Click" - Header="{locale:Locale GameListContextMenuExtractDataRomFS}" - ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" /> - <MenuItem - Click="ExtractApplicationLogo_Click" - Header="{locale:Locale GameListContextMenuExtractDataLogo}" - ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> - </MenuItem> -</MenuFlyout> diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs deleted file mode 100644 index 894ac6c1..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs +++ /dev/null @@ -1,371 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; -using Avalonia.Threading; -using LibHac.Fs; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -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 System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using Path = System.IO.Path; - -namespace Ryujinx.Ava.UI.Controls -{ - public class ApplicationContextMenu : MenuFlyout - { - public ApplicationContextMenu() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - - public void ToggleFavorite_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; - - ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata => - { - appMetadata.Favorite = viewModel.SelectedApplication.Favorite; - }); - - viewModel.RefreshView(); - } - } - - public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args) - { - if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) - { - OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); - } - } - - public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - OpenSaveDirectory(viewModel, SaveDataType.Device, default); - } - - public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); - } - - private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) - { - if (viewModel?.SelectedApplication != null) - { - if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); - }); - - return; - } - - var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default); - - ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName); - } - } - - public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); - } - } - - public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); - } - } - - public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await new CheatWindow( - viewModel.VirtualFileSystem, - viewModel.SelectedApplication.TitleId, - viewModel.SelectedApplication.TitleName, - viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window); - } - } - - public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId); - - OpenHelper.OpenFolder(titleModsPath); - } - } - - public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - string sdModsBasePath = ModLoader.GetSdModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId); - - OpenHelper.OpenFolder(titleModsPath); - } - } - - public async void OpenModManager_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await ModManagerWindow.Show(ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); - } - } - - public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0")); - DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1")); - - List<FileInfo> cacheFiles = new(); - - if (mainDir.Exists) - { - cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); - } - - if (backupDir.Exists) - { - cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); - } - - if (cacheFiles.Count > 0) - { - foreach (FileInfo file in cacheFiles) - { - try - { - file.Delete(); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); - } - } - } - } - } - } - - public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader")); - - List<DirectoryInfo> oldCacheDirectories = new(); - List<FileInfo> newCacheFiles = new(); - - if (shaderCacheDir.Exists) - { - oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); - } - - if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) - { - foreach (DirectoryInfo directory in oldCacheDirectories) - { - try - { - directory.Delete(true); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex)); - } - } - - foreach (FileInfo file in newCacheFiles) - { - try - { - file.Delete(); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex)); - } - } - } - } - } - } - - public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu"); - string mainDir = Path.Combine(ptcDir, "0"); - string backupDir = Path.Combine(ptcDir, "1"); - - if (!Directory.Exists(ptcDir)) - { - Directory.CreateDirectory(ptcDir); - Directory.CreateDirectory(mainDir); - Directory.CreateDirectory(backupDir); - } - - OpenHelper.OpenFolder(ptcDir); - } - } - - public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"); - - if (!Directory.Exists(shaderCacheDir)) - { - Directory.CreateDirectory(shaderCacheDir); - } - - OpenHelper.OpenFolder(shaderCacheDir); - } - } - - public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await ApplicationHelper.ExtractSection( - viewModel.StorageProvider, - NcaSectionType.Code, - viewModel.SelectedApplication.Path, - viewModel.SelectedApplication.TitleName); - } - } - - public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await ApplicationHelper.ExtractSection( - viewModel.StorageProvider, - NcaSectionType.Data, - viewModel.SelectedApplication.Path, - viewModel.SelectedApplication.TitleName); - } - } - - public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await ApplicationHelper.ExtractSection( - viewModel.StorageProvider, - NcaSectionType.Logo, - viewModel.SelectedApplication.Path, - viewModel.SelectedApplication.TitleName); - } - } - - public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - ApplicationData selectedApplication = viewModel.SelectedApplication; - ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon); - } - } - - public async void RunApplication_Click(object sender, RoutedEventArgs args) - { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - await viewModel.LoadApplication(viewModel.SelectedApplication.Path); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml deleted file mode 100644 index 2dc95662..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml +++ /dev/null @@ -1,102 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Controls.ApplicationGridView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - d:DesignHeight="450" - d:DesignWidth="800" - Focusable="True" - mc:Ignorable="d" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - x:DataType="viewModels:MainWindowViewModel"> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - <controls:ApplicationContextMenu x:Key="ApplicationContextMenu" /> - </UserControl.Resources> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="*" /> - </Grid.RowDefinitions> - <ListBox - Grid.Row="0" - Padding="8" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ContextFlyout="{StaticResource ApplicationContextMenu}" - DoubleTapped="GameList_DoubleTapped" - ItemsSource="{Binding AppsObservableList}" - SelectionChanged="GameList_SelectionChanged"> - <ListBox.ItemsPanel> - <ItemsPanelTemplate> - <WrapPanel - HorizontalAlignment="Center" - VerticalAlignment="Top" - Orientation="Horizontal" /> - </ItemsPanelTemplate> - </ListBox.ItemsPanel> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Margin" Value="5" /> - <Setter Property="CornerRadius" Value="4" /> - </Style> - <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> - <Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" /> - </Style> - </ListBox.Styles> - <ListBox.ItemTemplate> - <DataTemplate> - <Grid> - <Border - Margin="10" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" - Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" - Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" - Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" - ClipToBounds="True" - CornerRadius="4"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Image - Grid.Row="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Top" - Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> - <Panel - Grid.Row="1" - Height="50" - Margin="0,10,0,0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> - <TextBlock - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{Binding TitleName}" - TextAlignment="Center" - TextWrapping="Wrap" /> - </Panel> - </Grid> - </Border> - <ui:SymbolIcon - Margin="5,5,0,0" - HorizontalAlignment="Left" - VerticalAlignment="Top" - FontSize="16" - Foreground="{DynamicResource SystemAccentColor}" - IsVisible="{Binding Favorite}" - Symbol="StarFilled" /> - </Grid> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs deleted file mode 100644 index ee15bc8d..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.UI.App.Common; -using System; - -namespace Ryujinx.Ava.UI.Controls -{ - public partial class ApplicationGridView : UserControl - { - public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = - RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); - - public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened - { - add { AddHandler(ApplicationOpenedEvent, value); } - remove { RemoveHandler(ApplicationOpenedEvent, value); } - } - - public ApplicationGridView() - { - InitializeComponent(); - } - - public void GameList_DoubleTapped(object sender, TappedEventArgs args) - { - if (sender is ListBox listBox) - { - if (listBox.SelectedItem is ApplicationData selected) - { - RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); - } - } - } - - public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) - { - if (sender is ListBox listBox) - { - (DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData; - } - } - - private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) - { - (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml deleted file mode 100644 index fecf0888..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml +++ /dev/null @@ -1,160 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Controls.ApplicationListView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - d:DesignHeight="450" - d:DesignWidth="800" - Focusable="True" - mc:Ignorable="d" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - x:DataType="viewModels:MainWindowViewModel"> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - <controls:ApplicationContextMenu x:Key="ApplicationContextMenu" /> - </UserControl.Resources> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="*" /> - </Grid.RowDefinitions> - <ListBox - Name="GameListBox" - Grid.Row="0" - Padding="8" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ContextFlyout="{StaticResource ApplicationContextMenu}" - DoubleTapped="GameList_DoubleTapped" - ItemsSource="{Binding AppsObservableList}" - SelectionChanged="GameList_SelectionChanged"> - <ListBox.ItemsPanel> - <ItemsPanelTemplate> - <StackPanel - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Orientation="Vertical" - Spacing="2" /> - </ItemsPanelTemplate> - </ListBox.ItemsPanel> - <ListBox.Styles> - <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> - <Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" /> - </Style> - </ListBox.Styles> - <ListBox.ItemTemplate> - <DataTemplate> - <Grid> - <Border - Margin="0" - Padding="10" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ClipToBounds="True" - CornerRadius="5"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="10" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="150" /> - <ColumnDefinition Width="100" /> - </Grid.ColumnDefinitions> - <Image - Grid.RowSpan="3" - Grid.Column="0" - Margin="0" - Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" - Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" - Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" - Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" - Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> - <Border - Grid.Column="2" - Margin="0,0,5,0" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="0,0,1,0"> - <StackPanel - HorizontalAlignment="Left" - VerticalAlignment="Top" - Orientation="Vertical" - Spacing="5"> - <TextBlock - HorizontalAlignment="Stretch" - FontWeight="Bold" - Text="{Binding TitleName}" - TextAlignment="Start" - TextWrapping="Wrap" /> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding Developer}" - TextAlignment="Start" - TextWrapping="Wrap" /> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding Version}" - TextAlignment="Start" - TextWrapping="Wrap" /> - </StackPanel> - </Border> - <StackPanel - Grid.Column="3" - Margin="10,0,0,0" - HorizontalAlignment="Left" - VerticalAlignment="Top" - Orientation="Vertical" - Spacing="5"> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding TitleId}" - TextAlignment="Start" - TextWrapping="Wrap" /> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding FileExtension}" - TextAlignment="Start" - TextWrapping="Wrap" /> - </StackPanel> - <StackPanel - Grid.Column="4" - HorizontalAlignment="Right" - VerticalAlignment="Top" - Orientation="Vertical" - Spacing="5"> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding TimePlayedString}" - TextAlignment="End" - TextWrapping="Wrap" /> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}" - TextAlignment="End" - TextWrapping="Wrap" /> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding FileSizeString}" - TextAlignment="End" - TextWrapping="Wrap" /> - </StackPanel> - <ui:SymbolIcon - Grid.Row="0" - Grid.Column="0" - Margin="-5,-5,0,0" - HorizontalAlignment="Left" - VerticalAlignment="Top" - FontSize="16" - Foreground="{DynamicResource SystemAccentColor}" - IsVisible="{Binding Favorite}" - Symbol="StarFilled" /> - </Grid> - </Border> - </Grid> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs deleted file mode 100644 index 8681158f..00000000 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.UI.App.Common; -using System; - -namespace Ryujinx.Ava.UI.Controls -{ - public partial class ApplicationListView : UserControl - { - public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = - RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); - - public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened - { - add { AddHandler(ApplicationOpenedEvent, value); } - remove { RemoveHandler(ApplicationOpenedEvent, value); } - } - - public ApplicationListView() - { - InitializeComponent(); - } - - public void GameList_DoubleTapped(object sender, TappedEventArgs args) - { - if (sender is ListBox listBox) - { - if (listBox.SelectedItem is ApplicationData selected) - { - RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); - } - } - } - - public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) - { - if (sender is ListBox listBox) - { - (DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData; - } - } - - private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) - { - (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml b/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml deleted file mode 100644 index bf34b303..00000000 --- a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml +++ /dev/null @@ -1,17 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - mc:Ignorable="d" - d:DesignWidth="800" - d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Controls.NavigationDialogHost" - Focusable="True"> - <ui:Frame - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - x:Name="ContentFrame"> - </ui:Frame> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs deleted file mode 100644 index a32c052b..00000000 --- a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Styling; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using LibHac; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Shim; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ava.UI.Views.User; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; -using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; - -namespace Ryujinx.Ava.UI.Controls -{ - public partial class NavigationDialogHost : UserControl - { - public AccountManager AccountManager { get; } - public ContentManager ContentManager { get; } - public VirtualFileSystem VirtualFileSystem { get; } - public HorizonClient HorizonClient { get; } - public UserProfileViewModel ViewModel { get; set; } - - public NavigationDialogHost() - { - InitializeComponent(); - } - - public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, - VirtualFileSystem virtualFileSystem, HorizonClient horizonClient) - { - AccountManager = accountManager; - ContentManager = contentManager; - VirtualFileSystem = virtualFileSystem; - HorizonClient = horizonClient; - ViewModel = new UserProfileViewModel(); - LoadProfiles(); - - if (contentManager.GetCurrentFirmwareVersion() != null) - { - Task.Run(() => - { - UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem); - }); - } - InitializeComponent(); - } - - public void GoBack() - { - if (ContentFrame.BackStack.Count > 0) - { - ContentFrame.GoBack(); - } - - LoadProfiles(); - } - - public void Navigate(Type sourcePageType, object parameter) - { - ContentFrame.Navigate(sourcePageType, parameter); - } - - public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, - VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient) - { - var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); - ContentDialog contentDialog = new() - { - Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], - PrimaryButtonText = "", - SecondaryButtonText = "", - CloseButtonText = "", - Content = content, - Padding = new Thickness(0), - }; - - contentDialog.Closed += (sender, args) => - { - content.ViewModel.Dispose(); - }; - - Style footer = new(x => x.Name("DialogSpace").Child().OfType<Border>()); - footer.Setters.Add(new Setter(IsVisibleProperty, false)); - - contentDialog.Styles.Add(footer); - - await contentDialog.ShowAsync(); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - Navigate(typeof(UserSelectorViews), this); - } - - public void LoadProfiles() - { - ViewModel.Profiles.Clear(); - ViewModel.LostProfiles.Clear(); - - var profiles = AccountManager.GetAllUsers().OrderBy(x => x.Name); - - foreach (var profile in profiles) - { - ViewModel.Profiles.Add(new UserProfile(profile, this)); - } - - var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, default, saveDataId: default, index: default); - - using var saveDataIterator = new UniqueRef<SaveDataIterator>(); - - HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); - - Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10]; - - HashSet<UserId> lostAccounts = new(); - - while (true) - { - saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); - - if (readCount == 0) - { - break; - } - - for (int i = 0; i < readCount; i++) - { - var save = saveDataInfo[i]; - var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High); - if (ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId == id) == null) - { - lostAccounts.Add(id); - } - } - } - - foreach (var account in lostAccounts) - { - ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this)); - } - - ViewModel.Profiles.Add(new BaseModel()); - } - - public async void DeleteUser(UserProfile userProfile) - { - var lastUserId = AccountManager.LastOpenedUser.UserId; - - if (userProfile.UserId == lastUserId) - { - // If we are deleting the currently open profile, then we must open something else before deleting. - var profile = ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId != lastUserId); - - if (profile == null) - { - static async void Action() - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]); - } - - Dispatcher.UIThread.Post(Action); - - return; - } - - AccountManager.OpenUser(profile.UserId); - } - - var result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage], - "", - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - ""); - - if (result == UserResult.Yes) - { - GoBack(); - AccountManager.DeleteUser(userProfile.UserId); - } - - LoadProfiles(); - } - - public void AddUser() - { - Navigate(typeof(UserEditorView), (this, (UserProfile)null, true)); - } - - public void EditUser(UserProfile userProfile) - { - Navigate(typeof(UserEditorView), (this, userProfile, false)); - } - - public void RecoverLostAccounts() - { - Navigate(typeof(UserRecovererView), this); - } - - public void ManageSaves() - { - Navigate(typeof(UserSaveManagerView), (this, AccountManager, HorizonClient, VirtualFileSystem)); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs b/src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs deleted file mode 100644 index b57c6f0a..00000000 --- a/src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using System; - -namespace Ryujinx.Ava.UI.Controls -{ - public class SliderScroll : Slider - { - protected override Type StyleKeyOverride => typeof(Slider); - - protected override void OnPointerWheelChanged(PointerWheelEventArgs e) - { - var newValue = Value + e.Delta.Y * TickFrequency; - - if (newValue < Minimum) - { - Value = Minimum; - } - else if (newValue > Maximum) - { - Value = Maximum; - } - else - { - Value = newValue; - } - - e.Handled = true; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml deleted file mode 100644 index 09fa0404..00000000 --- a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml +++ /dev/null @@ -1,42 +0,0 @@ -<Window - x:Class="Ryujinx.Ava.UI.Controls.UpdateWaitWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="Ryujinx - Waiting" - SizeToContent="WidthAndHeight" - WindowStartupLocation="CenterOwner" - mc:Ignorable="d" - Focusable="True"> - <Grid - Margin="20" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Image - Grid.Row="1" - Height="70" - MinWidth="50" - Margin="5,10,20,10" - Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> - <StackPanel - Grid.Row="1" - Grid.Column="1" - VerticalAlignment="Center" - Orientation="Vertical"> - <TextBlock Name="PrimaryText" Margin="5" /> - <TextBlock - Name="SecondaryText" - Margin="5" - VerticalAlignment="Center" /> - </StackPanel> - </Grid> -</Window> diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs deleted file mode 100644 index 7ad1ee33..00000000 --- a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Avalonia.Controls; -using Ryujinx.Ava.UI.Windows; -using System.Threading; - -namespace Ryujinx.Ava.UI.Controls -{ - public partial class UpdateWaitWindow : StyleableWindow - { - public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText) - { - SystemDecorations = SystemDecorations.Full; - ShowInTaskbar = true; - - Closing += (_, _) => cancellationToken.Cancel(); - } - - public UpdateWaitWindow(string primaryText, string secondaryText) : this() - { - PrimaryText.Text = primaryText; - SecondaryText.Text = secondaryText; - WindowStartupLocation = WindowStartupLocation.CenterOwner; - SystemDecorations = SystemDecorations.BorderOnly; - ShowInTaskbar = false; - } - - public UpdateWaitWindow() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs deleted file mode 100644 index bc5622b5..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Avalonia.Interactivity; -using Ryujinx.UI.App.Common; - -namespace Ryujinx.Ava.UI.Helpers -{ - public class ApplicationOpenedEventArgs : RoutedEventArgs - { - public ApplicationData Application { get; } - - public ApplicationOpenedEventArgs(ApplicationData application, RoutedEvent routedEvent) - { - Application = application; - RoutedEvent = routedEvent; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs b/src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs deleted file mode 100644 index 42bd8d5a..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Avalonia.Data.Converters; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using System; -using System.Globalization; -using System.IO; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal class BitmapArrayValueConverter : IValueConverter - { - public static BitmapArrayValueConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == null) - { - return null; - } - - if (value is byte[] buffer && targetType == typeof(IImage)) - { - MemoryStream mem = new(buffer); - - return new Bitmap(mem); - } - - throw new NotSupportedException(); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs deleted file mode 100644 index 7e8ba734..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.LogicalTree; -using Avalonia.Threading; -using Ryujinx.Input; -using Ryujinx.Input.Assigner; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal class ButtonKeyAssigner - { - internal class ButtonAssignedEventArgs : EventArgs - { - public ToggleButton Button { get; } - public bool IsAssigned { get; } - - public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned) - { - Button = button; - IsAssigned = isAssigned; - } - } - - public ToggleButton ToggledButton { get; set; } - - private bool _isWaitingForInput; - private bool _shouldUnbind; - public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned; - - public ButtonKeyAssigner(ToggleButton toggleButton) - { - ToggledButton = toggleButton; - } - - public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) - { - Dispatcher.UIThread.Post(() => - { - ToggledButton.IsChecked = true; - }); - - if (_isWaitingForInput) - { - Dispatcher.UIThread.Post(() => - { - Cancel(); - }); - - return; - } - - _isWaitingForInput = true; - - assigner.Initialize(); - - await Task.Run(async () => - { - while (true) - { - if (!_isWaitingForInput) - { - return; - } - - await Task.Delay(10); - - assigner.ReadInput(); - - if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) - { - break; - } - } - }); - - await Dispatcher.UIThread.InvokeAsync(() => - { - string pressedButton = assigner.GetPressedButton(); - - if (_shouldUnbind) - { - SetButtonText(ToggledButton, "Unbound"); - } - else if (pressedButton != "") - { - SetButtonText(ToggledButton, pressedButton); - } - - _shouldUnbind = false; - _isWaitingForInput = false; - - ToggledButton.IsChecked = false; - - ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null)); - - static void SetButtonText(ToggleButton button, string text) - { - ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock); - - if (textBlock != null && textBlock is TextBlock block) - { - block.Text = text; - } - } - }); - } - - public void Cancel(bool shouldUnbind = false) - { - _isWaitingForInput = false; - ToggledButton.IsChecked = false; - _shouldUnbind = shouldUnbind; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs deleted file mode 100644 index 15b7ddd1..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ /dev/null @@ -1,425 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Threading; -using FluentAvalonia.Core; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Helpers -{ - public static class ContentDialogHelper - { - private static bool _isChoiceDialogOpen; - private static ContentDialogOverlayWindow _contentDialogOverlayWindow; - - private async static Task<UserResult> ShowContentDialog( - string title, - object content, - string primaryButton, - string secondaryButton, - string closeButton, - UserResult primaryButtonResult = UserResult.Ok, - ManualResetEvent deferResetEvent = null, - TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) - { - UserResult result = UserResult.None; - - ContentDialog contentDialog = new() - { - Title = title, - PrimaryButtonText = primaryButton, - SecondaryButtonText = secondaryButton, - CloseButtonText = closeButton, - Content = content, - PrimaryButtonCommand = MiniCommand.Create(() => - { - result = primaryButtonResult; - }), - }; - - contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => - { - result = UserResult.No; - contentDialog.PrimaryButtonClick -= deferCloseAction; - }); - - contentDialog.CloseButtonCommand = MiniCommand.Create(() => - { - result = UserResult.Cancel; - contentDialog.PrimaryButtonClick -= deferCloseAction; - }); - - if (deferResetEvent != null) - { - contentDialog.PrimaryButtonClick += deferCloseAction; - } - - await ShowAsync(contentDialog); - - return result; - } - - public async static Task<UserResult> ShowTextDialog( - string title, - string primaryText, - string secondaryText, - string primaryButton, - string secondaryButton, - string closeButton, - int iconSymbol, - UserResult primaryButtonResult = UserResult.Ok, - ManualResetEvent deferResetEvent = null, - TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) - { - Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol); - - return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); - } - - public async static Task<UserResult> ShowDeferredContentDialog( - StyleableWindow window, - string title, - string primaryText, - string secondaryText, - string primaryButton, - string secondaryButton, - string closeButton, - int iconSymbol, - ManualResetEvent deferResetEvent, - Func<Window, Task> doWhileDeferred = null) - { - bool startedDeferring = false; - - return await ShowTextDialog( - title, - primaryText, - secondaryText, - primaryButton, - secondaryButton, - closeButton, - iconSymbol, - primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok, - deferResetEvent, - DeferClose); - - async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args) - { - if (startedDeferring) - { - return; - } - - sender.PrimaryButtonClick -= DeferClose; - - startedDeferring = true; - - var deferral = args.GetDeferral(); - - sender.PrimaryButtonClick -= DeferClose; - - _ = Task.Run(() => - { - deferResetEvent.WaitOne(); - - Dispatcher.UIThread.Post(() => - { - deferral.Complete(); - }); - }); - - if (doWhileDeferred != null) - { - await doWhileDeferred(window); - - deferResetEvent.Set(); - } - } - } - - private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol) - { - Grid content = new() - { - RowDefinitions = new RowDefinitions { new(), new() }, - ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() }, - - MinHeight = 80, - }; - - SymbolIcon icon = new() - { - Symbol = (Symbol)symbol, - Margin = new Thickness(10), - FontSize = 40, - VerticalAlignment = VerticalAlignment.Center, - }; - - Grid.SetColumn(icon, 0); - Grid.SetRowSpan(icon, 2); - Grid.SetRow(icon, 0); - - TextBlock primaryLabel = new() - { - Text = primaryText, - Margin = new Thickness(5), - TextWrapping = TextWrapping.Wrap, - MaxWidth = 450, - }; - - TextBlock secondaryLabel = new() - { - Text = secondaryText, - Margin = new Thickness(5), - TextWrapping = TextWrapping.Wrap, - MaxWidth = 450, - }; - - Grid.SetColumn(primaryLabel, 1); - Grid.SetColumn(secondaryLabel, 1); - Grid.SetRow(primaryLabel, 0); - Grid.SetRow(secondaryLabel, 1); - - content.Children.Add(icon); - content.Children.Add(primaryLabel); - content.Children.Add(secondaryLabel); - - return content; - } - - public static async Task<UserResult> CreateInfoDialog( - string primary, - string secondaryText, - string acceptButton, - string closeButton, - string title) - { - return await ShowTextDialog( - title, - primary, - secondaryText, - acceptButton, - "", - closeButton, - (int)Symbol.Important); - } - - internal static async Task<UserResult> CreateConfirmationDialog( - string primaryText, - string secondaryText, - string acceptButtonText, - string cancelButtonText, - string title, - UserResult primaryButtonResult = UserResult.Yes) - { - return await ShowTextDialog( - string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title, - primaryText, - secondaryText, - acceptButtonText, - "", - cancelButtonText, - (int)Symbol.Help, - primaryButtonResult); - } - - internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) - { - await ShowTextDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], - primary, - secondaryText, - "", - "", - LocaleManager.Instance[LocaleKeys.InputDialogOk], - (int)Symbol.Important); - } - - internal static async Task CreateWarningDialog(string primary, string secondaryText) - { - await ShowTextDialog( - LocaleManager.Instance[LocaleKeys.DialogWarningTitle], - primary, - secondaryText, - "", - "", - LocaleManager.Instance[LocaleKeys.InputDialogOk], - (int)Symbol.Important); - } - - internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "") - { - Logger.Error?.Print(LogClass.Application, errorMessage); - - await ShowTextDialog( - LocaleManager.Instance[LocaleKeys.DialogErrorTitle], - LocaleManager.Instance[LocaleKeys.DialogErrorMessage], - errorMessage, - secondaryErrorMessage, - "", - LocaleManager.Instance[LocaleKeys.InputDialogOk], - (int)Symbol.Dismiss); - } - - internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText) - { - if (_isChoiceDialogOpen) - { - return false; - } - - _isChoiceDialogOpen = true; - - UserResult response = await ShowTextDialog( - title, - primary, - secondaryText, - LocaleManager.Instance[LocaleKeys.InputDialogYes], - "", - LocaleManager.Instance[LocaleKeys.InputDialogNo], - (int)Symbol.Help, - UserResult.Yes); - - _isChoiceDialogOpen = false; - - return response == UserResult.Yes; - } - - internal static async Task<bool> CreateExitDialog() - { - return await CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.DialogExitTitle], - LocaleManager.Instance[LocaleKeys.DialogExitMessage], - LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); - } - - internal static async Task<bool> CreateStopEmulationDialog() - { - return await CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.DialogStopEmulationTitle], - LocaleManager.Instance[LocaleKeys.DialogStopEmulationMessage], - LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); - } - - public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog) - { - ContentDialogResult result; - bool isTopDialog = true; - - Window parent = GetMainWindow(); - - if (_contentDialogOverlayWindow != null) - { - isTopDialog = false; - } - - if (parent is MainWindow window) - { - parent.Activate(); - - _contentDialogOverlayWindow = new ContentDialogOverlayWindow - { - Height = parent.Bounds.Height, - Width = parent.Bounds.Width, - Position = parent.PointToScreen(new Point()), - ShowInTaskbar = false, - }; - - parent.PositionChanged += OverlayOnPositionChanged; - - void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) - { - if (_contentDialogOverlayWindow is null) - { - return; - } - - _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); - } - - _contentDialogOverlayWindow.ContentDialog = contentDialog; - - bool opened = false; - - _contentDialogOverlayWindow.Opened += OverlayOnActivated; - - async void OverlayOnActivated(object sender, EventArgs e) - { - if (opened) - { - return; - } - - opened = true; - - _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); - - result = await ShowDialog(); - } - - result = await _contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent); - } - else - { - result = await ShowDialog(); - } - - async Task<ContentDialogResult> ShowDialog() - { - if (_contentDialogOverlayWindow is not null) - { - result = await contentDialog.ShowAsync(_contentDialogOverlayWindow); - - _contentDialogOverlayWindow!.Close(); - } - else - { - result = ContentDialogResult.None; - - Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned."); - } - - return result; - } - - if (isTopDialog && _contentDialogOverlayWindow is not null) - { - _contentDialogOverlayWindow.Content = null; - _contentDialogOverlayWindow.Close(); - _contentDialogOverlayWindow = null; - } - - return result; - } - - public static Task ShowWindowAsync(Window dialogWindow, Window mainWindow = null) - { - mainWindow ??= GetMainWindow(); - - return dialogWindow.ShowDialog(_contentDialogOverlayWindow ?? mainWindow); - } - - private static Window GetMainWindow() - { - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) - { - foreach (Window item in al.Windows) - { - if (item is MainWindow window) - { - return window; - } - } - } - - return null; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/Glyph.cs b/src/Ryujinx.Ava/UI/Helpers/Glyph.cs deleted file mode 100644 index f257dc02..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/Glyph.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Ava.UI.Helpers -{ - public enum Glyph - { - List, - Grid, - Chip, - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs b/src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs deleted file mode 100644 index 7da23648..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Avalonia.Markup.Xaml; -using FluentAvalonia.UI.Controls; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Ava.UI.Helpers -{ - public class GlyphValueConverter : MarkupExtension - { - private readonly string _key; - - private static readonly Dictionary<Glyph, string> _glyphs = new() - { - { Glyph.List, char.ConvertFromUtf32((int)Symbol.List) }, - { Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll) }, - { Glyph.Chip, char.ConvertFromUtf32(59748) }, - }; - - public GlyphValueConverter(string key) - { - _key = key; - } - - public string this[string key] - { - get - { - if (_glyphs.TryGetValue(Enum.Parse<Glyph>(key), out var val)) - { - return val; - } - - return string.Empty; - } - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - return this[_key]; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs deleted file mode 100644 index 028ed6bf..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Avalonia.Data.Converters; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using System; -using System.Globalization; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal class KeyValueConverter : IValueConverter - { - public static KeyValueConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == null) - { - return null; - } - - return value.ToString(); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - object key = null; - - if (value != null) - { - if (targetType == typeof(Key)) - { - key = Enum.Parse<Key>(value.ToString()); - } - else if (targetType == typeof(GamepadInputId)) - { - key = Enum.Parse<GamepadInputId>(value.ToString()); - } - else if (targetType == typeof(StickInputId)) - { - key = Enum.Parse<StickInputId>(value.ToString()); - } - } - - return key; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs deleted file mode 100644 index 26fe36c4..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Avalonia.Data.Converters; -using Avalonia.Markup.Xaml; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.UI.Common.Helper; -using System; -using System.Globalization; - -namespace Ryujinx.Ava.UI.Helpers -{ - /// <summary> - /// This <see cref="IValueConverter"/> makes sure that the string "Never" that's returned by <see cref="ValueFormatUtils.FormatDateTime"/> is properly localized in the Avalonia UI. - /// After the Avalonia UI has been made the default and the GTK UI is removed, <see cref="ValueFormatUtils"/> should be updated to directly return a localized string. - /// </summary> - internal class LocalizedNeverConverter : MarkupExtension, IValueConverter - { - private static readonly LocalizedNeverConverter _instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not string valStr) - { - return ""; - } - - if (valStr == "Never") - { - return LocaleManager.Instance[LocaleKeys.Never]; - } - - return valStr; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - return _instance; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs deleted file mode 100644 index fc714541..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Avalonia.Logging; -using Avalonia.Utilities; -using Ryujinx.Common.Logging; -using System; -using System.Text; - -namespace Ryujinx.Ava.UI.Helpers -{ - using AvaLogger = Avalonia.Logging.Logger; - using AvaLogLevel = LogEventLevel; - using RyuLogClass = LogClass; - using RyuLogger = Ryujinx.Common.Logging.Logger; - - internal class LoggerAdapter : ILogSink - { - public static void Register() - { - AvaLogger.Sink = new LoggerAdapter(); - } - - private static RyuLogger.Log? GetLog(AvaLogLevel level) - { - return level switch - { - AvaLogLevel.Verbose => RyuLogger.Debug, - AvaLogLevel.Debug => RyuLogger.Debug, - AvaLogLevel.Information => RyuLogger.Debug, - AvaLogLevel.Warning => RyuLogger.Debug, - AvaLogLevel.Error => RyuLogger.Error, - AvaLogLevel.Fatal => RyuLogger.Error, - _ => throw new ArgumentOutOfRangeException(nameof(level), level, null), - }; - } - - public bool IsEnabled(AvaLogLevel level, string area) - { - return GetLog(level) != null; - } - - public void Log(AvaLogLevel level, string area, object source, string messageTemplate) - { - 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)); - } - - private static string Format(AvaLogLevel level, string area, string template, object source, object[] v) - { - var result = new StringBuilder(); - var r = new CharacterReader(template.AsSpan()); - int i = 0; - - result.Append('['); - result.Append(level); - result.Append("] "); - - result.Append('['); - result.Append(area); - result.Append("] "); - - while (!r.End) - { - var c = r.Take(); - - if (c != '{') - { - result.Append(c); - } - else - { - if (r.Peek != '{') - { - result.Append('\''); - result.Append(i < v.Length ? v[i++] : null); - result.Append('\''); - r.TakeUntil('}'); - r.Take(); - } - else - { - result.Append('{'); - r.Take(); - } - } - } - - if (source != null) - { - result.Append(" ("); - result.Append(source.GetType().Name); - result.Append(" #"); - result.Append(source.GetHashCode()); - result.Append(')'); - } - - return result.ToString(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs b/src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs deleted file mode 100644 index 7e1bb9a6..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; - -namespace Ryujinx.Ava.UI.Helpers -{ - public sealed class MiniCommand<T> : MiniCommand, ICommand - { - private readonly Action<T> _callback; - private bool _busy; - private readonly Func<T, Task> _asyncCallback; - - public MiniCommand(Action<T> callback) - { - _callback = callback; - } - - public MiniCommand(Func<T, Task> callback) - { - _asyncCallback = callback; - } - - private bool Busy - { - get => _busy; - set - { - _busy = value; - CanExecuteChanged?.Invoke(this, EventArgs.Empty); - } - } - - public override event EventHandler CanExecuteChanged; - public override bool CanExecute(object parameter) => !_busy; - - public override async void Execute(object parameter) - { - if (Busy) - { - return; - } - try - { - Busy = true; - if (_callback != null) - { - _callback((T)parameter); - } - else - { - await _asyncCallback((T)parameter); - } - } - finally - { - Busy = false; - } - } - } - - public abstract class MiniCommand : ICommand - { - public static MiniCommand Create(Action callback) => new MiniCommand<object>(_ => callback()); - public static MiniCommand Create<TArg>(Action<TArg> callback) => new MiniCommand<TArg>(callback); - public static MiniCommand CreateFromTask(Func<Task> callback) => new MiniCommand<object>(_ => callback()); - - public abstract bool CanExecute(object parameter); - public abstract void Execute(object parameter); - public abstract event EventHandler CanExecuteChanged; - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs b/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs deleted file mode 100644 index 656a8b52..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Notifications; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Common; -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.Ava.UI.Helpers -{ - public static class NotificationHelper - { - private const int MaxNotifications = 4; - private const int NotificationDelayInMs = 5000; - - private static WindowNotificationManager _notificationManager; - - private static readonly BlockingCollection<Notification> _notifications = new(); - - public static void SetNotificationManager(Window host) - { - _notificationManager = new WindowNotificationManager(host) - { - Position = NotificationPosition.BottomRight, - MaxItems = MaxNotifications, - Margin = new Thickness(0, 0, 15, 40), - }; - - var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>( - () => new AsyncWorkQueue<Notification>(notification => - { - Dispatcher.UIThread.Post(() => - { - _notificationManager.Show(notification); - }); - }, - "UI.NotificationThread", - _notifications), - LazyThreadSafetyMode.ExecutionAndPublication); - - _notificationManager.TemplateApplied += (sender, args) => - { - // NOTE: Force creation of the AsyncWorkQueue. - _ = maybeAsyncWorkQueue.Value; - }; - - host.Closing += (sender, args) => - { - if (maybeAsyncWorkQueue.IsValueCreated) - { - maybeAsyncWorkQueue.Value.Dispose(); - } - }; - } - - public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null) - { - var delay = waitingExit ? TimeSpan.FromMilliseconds(0) : TimeSpan.FromMilliseconds(NotificationDelayInMs); - - _notifications.Add(new Notification(title, text, type, delay, onClick, onClose)); - } - - public static void ShowError(string message) - { - Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs deleted file mode 100644 index a055f335..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; - -namespace Ryujinx.Ava.UI.Helpers -{ - public class OffscreenTextBox : TextBox - { - public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent() - { - return KeyDownEvent; - } - - public static RoutedEvent<KeyEventArgs> GetKeyUpRoutedEvent() - { - return KeyUpEvent; - } - - public void SendKeyDownEvent(KeyEventArgs keyEvent) - { - OnKeyDown(keyEvent); - } - - public void SendKeyUpEvent(KeyEventArgs keyEvent) - { - OnKeyUp(keyEvent); - } - - public void SendText(string text) - { - OnTextInput(new TextInputEventArgs - { - Text = text, - Source = this, - RoutedEvent = TextInputEvent, - }); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs b/src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs deleted file mode 100644 index 876d51f7..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Avalonia.Data.Converters; -using System; -using System.Globalization; -using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal class TimeZoneConverter : IValueConverter - { - public static TimeZoneConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == null) - { - return null; - } - - var timeZone = (TimeZone)value; - return string.Format("{0} {1} {2}", timeZone.UtcDifference, timeZone.Location, timeZone.Abbreviation); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs deleted file mode 100644 index 9a44b862..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Ryujinx.Ava.Common.Locale; -using Ryujinx.UI.Common; -using Ryujinx.UI.Common.Helper; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal class UserErrorDialog - { - private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; - - private static string GetErrorCode(UserError error) - { - return $"RYU-{(uint)error:X4}"; - } - - private static string GetErrorTitle(UserError error) - { - return error switch - { - UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys], - UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware], - UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed], - UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound], - UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown], - _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined], - }; - } - - private static string GetErrorDescription(UserError error) - { - return error switch - { - UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription], - UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription], - UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription], - UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription], - UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription], - _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription], - }; - } - - private static bool IsCoveredBySetupGuide(UserError error) - { - return error switch - { - UserError.NoKeys or - UserError.NoFirmware or - UserError.FirmwareParsingFailed => true, - _ => false, - }; - } - - private static string GetSetupGuideUrl(UserError error) - { - if (!IsCoveredBySetupGuide(error)) - { - return null; - } - - return error switch - { - UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", - UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", - _ => SetupGuideUrl, - }; - } - - public static async Task ShowUserErrorDialog(UserError error) - { - string errorCode = GetErrorCode(error); - - bool isInSetupGuide = IsCoveredBySetupGuide(error); - - string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : ""; - - var result = await ContentDialogHelper.CreateInfoDialog( - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)), - GetErrorDescription(error) + (isInSetupGuide - ? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage] - : ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode)); - - if (result == UserResult.Ok) - { - OpenHelper.OpenUrl(GetSetupGuideUrl(error)); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/UserResult.cs b/src/Ryujinx.Ava/UI/Helpers/UserResult.cs deleted file mode 100644 index 2fcd35ae..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/UserResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Ava.UI.Helpers -{ - public enum UserResult - { - Ok, - Yes, - No, - Abort, - Cancel, - None, - } -} diff --git a/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs deleted file mode 100644 index 4834df80..00000000 --- a/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace Ryujinx.Ava.UI.Helpers -{ - [SupportedOSPlatform("windows")] - internal partial class Win32NativeInterop - { - [Flags] - public enum ClassStyles : uint - { - CsClassdc = 0x40, - CsOwndc = 0x20, - } - - [Flags] - public enum WindowStyles : uint - { - WsChild = 0x40000000, - } - - public enum Cursors : uint - { - IdcArrow = 32512, - } - - [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] - public enum WindowsMessages : uint - { - Mousemove = 0x0200, - Lbuttondown = 0x0201, - Lbuttonup = 0x0202, - Lbuttondblclk = 0x0203, - Rbuttondown = 0x0204, - Rbuttonup = 0x0205, - Rbuttondblclk = 0x0206, - Mbuttondown = 0x0207, - Mbuttonup = 0x0208, - Mbuttondblclk = 0x0209, - Mousewheel = 0x020A, - Xbuttondown = 0x020B, - Xbuttonup = 0x020C, - Xbuttondblclk = 0x020D, - Mousehwheel = 0x020E, - Mouselast = 0x020E, - } - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); - - [StructLayout(LayoutKind.Sequential)] - public struct WndClassEx - { - public int cbSize; - public ClassStyles style; - public IntPtr lpfnWndProc; // not WndProc - public int cbClsExtra; - public int cbWndExtra; - public IntPtr hInstance; - public IntPtr hIcon; - public IntPtr hCursor; - public IntPtr hbrBackground; - public IntPtr lpszMenuName; - public IntPtr lpszClassName; - public IntPtr hIconSm; - - public WndClassEx() - { - cbSize = Marshal.SizeOf<WndClassEx>(); - } - } - - public static IntPtr CreateEmptyCursor() - { - return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 }); - } - - public static IntPtr CreateArrowCursor() - { - return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IdcArrow); - } - - [LibraryImport("user32.dll")] - public static partial IntPtr SetCursor(IntPtr handle); - - [LibraryImport("user32.dll")] - public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, [In] byte[] pvAndPlane, [In] byte[] pvXorPlane); - - [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")] - public static partial ushort RegisterClassEx(ref WndClassEx param); - - [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "UnregisterClassW")] - public static partial short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance); - - [LibraryImport("user32.dll", EntryPoint = "DefWindowProcW")] - public static partial IntPtr DefWindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); - - [LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleA")] - public static partial IntPtr GetModuleHandle([MarshalAs(UnmanagedType.LPStr)] string lpModuleName); - - [LibraryImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool DestroyWindow(IntPtr hwnd); - - [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "LoadCursorA")] - public static partial IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); - - [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowExW")] - public static partial IntPtr CreateWindowEx( - uint dwExStyle, - [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, - [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, - WindowStyles dwStyle, - int x, - int y, - int nWidth, - int nHeight, - IntPtr hWndParent, - IntPtr hMenu, - IntPtr hInstance, - IntPtr lpParam); - } -} diff --git a/src/Ryujinx.Ava/UI/Models/CheatNode.cs b/src/Ryujinx.Ava/UI/Models/CheatNode.cs deleted file mode 100644 index 8e9aee25..00000000 --- a/src/Ryujinx.Ava/UI/Models/CheatNode.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; - -namespace Ryujinx.Ava.UI.Models -{ - public class CheatNode : BaseModel - { - private bool _isEnabled = false; - public ObservableCollection<CheatNode> SubNodes { get; } = new(); - public string CleanName => Name[1..^7]; - public string BuildIdKey => $"{BuildId}-{Name}"; - public bool IsRootNode { get; } - public string Name { get; } - public string BuildId { get; } - public string Path { get; } - public bool IsEnabled - { - get - { - if (SubNodes.Count > 0) - { - return SubNodes.ToList().TrueForAll(x => x.IsEnabled); - } - - return _isEnabled; - } - set - { - foreach (var cheat in SubNodes) - { - cheat.IsEnabled = value; - cheat.OnPropertyChanged(); - } - - _isEnabled = value; - } - } - - public CheatNode(string name, string buildId, string path, bool isRootNode, bool isEnabled = false) - { - Name = name; - BuildId = buildId; - Path = path; - IsEnabled = isEnabled; - IsRootNode = isRootNode; - - SubNodes.CollectionChanged += CheatsList_CollectionChanged; - } - - private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(IsEnabled)); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/ControllerModel.cs b/src/Ryujinx.Ava/UI/Models/ControllerModel.cs deleted file mode 100644 index 98de7757..00000000 --- a/src/Ryujinx.Ava/UI/Models/ControllerModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; - -namespace Ryujinx.Ava.UI.Models -{ - internal record ControllerModel(ControllerType Type, string Name); -} diff --git a/src/Ryujinx.Ava/UI/Models/DeviceType.cs b/src/Ryujinx.Ava/UI/Models/DeviceType.cs deleted file mode 100644 index bb4fc3b3..00000000 --- a/src/Ryujinx.Ava/UI/Models/DeviceType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Ava.UI.Models -{ - public enum DeviceType - { - None, - Keyboard, - Controller, - } -} diff --git a/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs b/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs deleted file mode 100644 index 9e400441..00000000 --- a/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using System.IO; - -namespace Ryujinx.Ava.UI.Models -{ - public class DownloadableContentModel : BaseModel - { - private bool _enabled; - - public bool Enabled - { - get => _enabled; - set - { - _enabled = value; - - OnPropertyChanged(); - } - } - - public string TitleId { get; } - public string ContainerPath { get; } - public string FullPath { get; } - - public string FileName => Path.GetFileName(ContainerPath); - - public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled) - { - TitleId = titleId; - ContainerPath = containerPath; - FullPath = fullPath; - Enabled = enabled; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs deleted file mode 100644 index 224f78f4..00000000 --- a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.UI.App.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Ava.UI.Models.Generic -{ - internal class LastPlayedSortComparer : IComparer<ApplicationData> - { - public LastPlayedSortComparer() { } - public LastPlayedSortComparer(bool isAscending) { IsAscending = isAscending; } - - public bool IsAscending { get; } - - public int Compare(ApplicationData x, ApplicationData y) - { - DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch; - - if (x?.LastPlayed != null) - { - aValue = x.LastPlayed.Value; - } - - if (y?.LastPlayed != null) - { - bValue = y.LastPlayed.Value; - } - - return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs deleted file mode 100644 index f0fb035d..00000000 --- a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.UI.App.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Ava.UI.Models.Generic -{ - internal class TimePlayedSortComparer : IComparer<ApplicationData> - { - public TimePlayedSortComparer() { } - public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; } - - public bool IsAscending { get; } - - public int Compare(ApplicationData x, ApplicationData y) - { - TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero; - - if (x?.TimePlayed != null) - { - aValue = x.TimePlayed; - } - - if (y?.TimePlayed != null) - { - bValue = y.TimePlayed; - } - - return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs b/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs deleted file mode 100644 index f1352c6d..00000000 --- a/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs +++ /dev/null @@ -1,456 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using System; - -namespace Ryujinx.Ava.UI.Models -{ - internal class InputConfiguration<TKey, TStick> : BaseModel - { - private float _deadzoneRight; - private float _triggerThreshold; - private float _deadzoneLeft; - private double _gyroDeadzone; - private int _sensitivity; - private bool _enableMotion; - private float _weakRumble; - private float _strongRumble; - private float _rangeLeft; - private float _rangeRight; - - public InputBackendType Backend { get; set; } - - /// <summary> - /// Controller id - /// </summary> - public string Id { get; set; } - - /// <summary> - /// Controller's Type - /// </summary> - public ControllerType ControllerType { get; set; } - - /// <summary> - /// Player's Index for the controller - /// </summary> - public PlayerIndex PlayerIndex { get; set; } - - public TStick LeftJoystick { get; set; } - public bool LeftInvertStickX { get; set; } - public bool LeftInvertStickY { get; set; } - public bool RightRotate90 { get; set; } - public TKey LeftControllerStickButton { get; set; } - - public TStick RightJoystick { get; set; } - public bool RightInvertStickX { get; set; } - public bool RightInvertStickY { get; set; } - public bool LeftRotate90 { get; set; } - public TKey RightControllerStickButton { get; set; } - - public float DeadzoneLeft - { - get => _deadzoneLeft; - set - { - _deadzoneLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeLeft - { - get => _rangeLeft; - set - { - _rangeLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float DeadzoneRight - { - get => _deadzoneRight; - set - { - _deadzoneRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeRight - { - get => _rangeRight; - set - { - _rangeRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float TriggerThreshold - { - get => _triggerThreshold; - set - { - _triggerThreshold = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public MotionInputBackendType MotionBackend { get; set; } - - public TKey ButtonMinus { get; set; } - public TKey ButtonL { get; set; } - public TKey ButtonZl { get; set; } - public TKey LeftButtonSl { get; set; } - public TKey LeftButtonSr { get; set; } - public TKey DpadUp { get; set; } - public TKey DpadDown { get; set; } - public TKey DpadLeft { get; set; } - public TKey DpadRight { get; set; } - - public TKey ButtonPlus { get; set; } - public TKey ButtonR { get; set; } - public TKey ButtonZr { get; set; } - public TKey RightButtonSl { get; set; } - public TKey RightButtonSr { get; set; } - public TKey ButtonX { get; set; } - public TKey ButtonB { get; set; } - public TKey ButtonY { get; set; } - public TKey ButtonA { get; set; } - - public TKey LeftStickUp { get; set; } - public TKey LeftStickDown { get; set; } - public TKey LeftStickLeft { get; set; } - public TKey LeftStickRight { get; set; } - public TKey LeftKeyboardStickButton { get; set; } - - public TKey RightStickUp { get; set; } - public TKey RightStickDown { get; set; } - public TKey RightStickLeft { get; set; } - public TKey RightStickRight { get; set; } - public TKey RightKeyboardStickButton { get; set; } - - public int Sensitivity - { - get => _sensitivity; - set - { - _sensitivity = value; - - OnPropertyChanged(); - } - } - - public double GyroDeadzone - { - get => _gyroDeadzone; - set - { - _gyroDeadzone = Math.Round(value, 3); - - OnPropertyChanged(); - } - } - - public bool EnableMotion - { - get => _enableMotion; set - { - _enableMotion = value; - - OnPropertyChanged(); - } - } - - public bool EnableCemuHookMotion { get; set; } - public int Slot { get; set; } - public int AltSlot { get; set; } - public bool MirrorInput { get; set; } - public string DsuServerHost { get; set; } - public int DsuServerPort { get; set; } - - public bool EnableRumble { get; set; } - public float WeakRumble - { - get => _weakRumble; set - { - _weakRumble = value; - - OnPropertyChanged(); - } - } - public float StrongRumble - { - get => _strongRumble; set - { - _strongRumble = value; - - OnPropertyChanged(); - } - } - - public InputConfiguration(InputConfig config) - { - if (config != null) - { - Backend = config.Backend; - Id = config.Id; - ControllerType = config.ControllerType; - PlayerIndex = config.PlayerIndex; - - if (config is StandardKeyboardInputConfig keyboardConfig) - { - LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp; - LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown; - LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft; - LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight; - LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton; - - RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp; - RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown; - RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft; - RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight; - RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL; - } - else if (config is StandardControllerInputConfig controllerConfig) - { - LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick; - LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX; - LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY; - LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW; - LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton; - - RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick; - RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX; - RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY; - RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW; - RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL; - - DeadzoneLeft = controllerConfig.DeadzoneLeft; - DeadzoneRight = controllerConfig.DeadzoneRight; - RangeLeft = controllerConfig.RangeLeft; - RangeRight = controllerConfig.RangeRight; - TriggerThreshold = controllerConfig.TriggerThreshold; - - if (controllerConfig.Motion != null) - { - EnableMotion = controllerConfig.Motion.EnableMotion; - MotionBackend = controllerConfig.Motion.MotionBackend; - GyroDeadzone = controllerConfig.Motion.GyroDeadzone; - Sensitivity = controllerConfig.Motion.Sensitivity; - - if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook) - { - EnableCemuHookMotion = true; - DsuServerHost = cemuHook.DsuServerHost; - DsuServerPort = cemuHook.DsuServerPort; - Slot = cemuHook.Slot; - AltSlot = cemuHook.AltSlot; - MirrorInput = cemuHook.MirrorInput; - } - - if (controllerConfig.Rumble != null) - { - EnableRumble = controllerConfig.Rumble.EnableRumble; - WeakRumble = controllerConfig.Rumble.WeakRumble; - StrongRumble = controllerConfig.Rumble.StrongRumble; - } - } - } - } - } - - public InputConfiguration() - { - } - - public InputConfig GetConfig() - { - if (Backend == InputBackendType.WindowKeyboard) - { - return new StandardKeyboardInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig<Key> - { - DpadUp = (Key)(object)DpadUp, - DpadDown = (Key)(object)DpadDown, - DpadLeft = (Key)(object)DpadLeft, - DpadRight = (Key)(object)DpadRight, - ButtonL = (Key)(object)ButtonL, - ButtonZl = (Key)(object)ButtonZl, - ButtonSl = (Key)(object)LeftButtonSl, - ButtonSr = (Key)(object)LeftButtonSr, - ButtonMinus = (Key)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig<Key> - { - ButtonA = (Key)(object)ButtonA, - ButtonB = (Key)(object)ButtonB, - ButtonX = (Key)(object)ButtonX, - ButtonY = (Key)(object)ButtonY, - ButtonPlus = (Key)(object)ButtonPlus, - ButtonSl = (Key)(object)RightButtonSl, - ButtonSr = (Key)(object)RightButtonSr, - ButtonR = (Key)(object)ButtonR, - ButtonZr = (Key)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigKeyboardStick<Key> - { - StickUp = (Key)(object)LeftStickUp, - StickDown = (Key)(object)LeftStickDown, - StickRight = (Key)(object)LeftStickRight, - StickLeft = (Key)(object)LeftStickLeft, - StickButton = (Key)(object)LeftKeyboardStickButton, - }, - RightJoyconStick = new JoyconConfigKeyboardStick<Key> - { - StickUp = (Key)(object)RightStickUp, - StickDown = (Key)(object)RightStickDown, - StickLeft = (Key)(object)RightStickLeft, - StickRight = (Key)(object)RightStickRight, - StickButton = (Key)(object)RightKeyboardStickButton, - }, - Version = InputConfig.CurrentVersion, - }; - - } - - if (Backend == InputBackendType.GamepadSDL2) - { - var config = new StandardControllerInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId> - { - DpadUp = (GamepadInputId)(object)DpadUp, - DpadDown = (GamepadInputId)(object)DpadDown, - DpadLeft = (GamepadInputId)(object)DpadLeft, - DpadRight = (GamepadInputId)(object)DpadRight, - ButtonL = (GamepadInputId)(object)ButtonL, - ButtonZl = (GamepadInputId)(object)ButtonZl, - ButtonSl = (GamepadInputId)(object)LeftButtonSl, - ButtonSr = (GamepadInputId)(object)LeftButtonSr, - ButtonMinus = (GamepadInputId)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig<GamepadInputId> - { - ButtonA = (GamepadInputId)(object)ButtonA, - ButtonB = (GamepadInputId)(object)ButtonB, - ButtonX = (GamepadInputId)(object)ButtonX, - ButtonY = (GamepadInputId)(object)ButtonY, - ButtonPlus = (GamepadInputId)(object)ButtonPlus, - ButtonSl = (GamepadInputId)(object)RightButtonSl, - ButtonSr = (GamepadInputId)(object)RightButtonSr, - ButtonR = (GamepadInputId)(object)ButtonR, - ButtonZr = (GamepadInputId)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId> - { - Joystick = (StickInputId)(object)LeftJoystick, - InvertStickX = LeftInvertStickX, - InvertStickY = LeftInvertStickY, - Rotate90CW = LeftRotate90, - StickButton = (GamepadInputId)(object)LeftControllerStickButton, - }, - RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId> - { - Joystick = (StickInputId)(object)RightJoystick, - InvertStickX = RightInvertStickX, - InvertStickY = RightInvertStickY, - Rotate90CW = RightRotate90, - StickButton = (GamepadInputId)(object)RightControllerStickButton, - }, - Rumble = new RumbleConfigController - { - EnableRumble = EnableRumble, - WeakRumble = WeakRumble, - StrongRumble = StrongRumble, - }, - Version = InputConfig.CurrentVersion, - DeadzoneLeft = DeadzoneLeft, - DeadzoneRight = DeadzoneRight, - RangeLeft = RangeLeft, - RangeRight = RangeRight, - TriggerThreshold = TriggerThreshold, - Motion = EnableCemuHookMotion - ? new CemuHookMotionConfigController - { - DsuServerHost = DsuServerHost, - DsuServerPort = DsuServerPort, - Slot = Slot, - AltSlot = AltSlot, - MirrorInput = MirrorInput, - MotionBackend = MotionInputBackendType.CemuHook, - } - : new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - }, - }; - - config.Motion.Sensitivity = Sensitivity; - config.Motion.EnableMotion = EnableMotion; - config.Motion.GyroDeadzone = GyroDeadzone; - - return config; - } - - return null; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/ModModel.cs b/src/Ryujinx.Ava/UI/Models/ModModel.cs deleted file mode 100644 index ee28ca5f..00000000 --- a/src/Ryujinx.Ava/UI/Models/ModModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using System.IO; - -namespace Ryujinx.Ava.UI.Models -{ - public class ModModel : BaseModel - { - private bool _enabled; - - public bool Enabled - { - get => _enabled; - set - { - _enabled = value; - OnPropertyChanged(); - } - } - - public bool InSd { get; } - public string Path { get; } - public string Name { get; } - - public ModModel(string path, string name, bool enabled, bool inSd) - { - Path = path; - Name = name; - Enabled = enabled; - InSd = inSd; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/PlayerModel.cs b/src/Ryujinx.Ava/UI/Models/PlayerModel.cs deleted file mode 100644 index a19852b9..00000000 --- a/src/Ryujinx.Ava/UI/Models/PlayerModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; - -namespace Ryujinx.Ava.UI.Models -{ - public record PlayerModel(PlayerIndex Id, string Name); -} diff --git a/src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs b/src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs deleted file mode 100644 index 99365dfc..00000000 --- a/src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Avalonia.Media; -using Ryujinx.Ava.UI.ViewModels; - -namespace Ryujinx.Ava.UI.Models -{ - public class ProfileImageModel : BaseModel - { - public ProfileImageModel(string name, byte[] data) - { - Name = name; - Data = data; - } - - public string Name { get; set; } - public byte[] Data { get; set; } - - private SolidColorBrush _backgroundColor = new(Colors.White); - - public SolidColorBrush BackgroundColor - { - get - { - return _backgroundColor; - } - set - { - _backgroundColor = value; - OnPropertyChanged(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx.Ava/UI/Models/SaveModel.cs deleted file mode 100644 index d6dea2f6..00000000 --- a/src/Ryujinx.Ava/UI/Models/SaveModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using LibHac.Fs; -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 System.IO; -using System.Linq; -using System.Threading.Tasks; -using Path = System.IO.Path; - -namespace Ryujinx.Ava.UI.Models -{ - public class SaveModel : BaseModel - { - private long _size; - - public ulong SaveId { get; } - public ProgramId TitleId { get; } - public string TitleIdString => $"{TitleId.Value:X16}"; - public UserId UserId { get; } - public bool InGameList { get; } - public string Title { get; } - public byte[] Icon { get; } - - public long Size - { - get => _size; set - { - _size = value; - SizeAvailable = true; - OnPropertyChanged(); - OnPropertyChanged(nameof(SizeString)); - OnPropertyChanged(nameof(SizeAvailable)); - } - } - - public bool SizeAvailable { get; set; } - - public string SizeString => ValueFormatUtils.FormatFileSize(Size); - - public SaveModel(SaveDataInfo info) - { - SaveId = info.SaveDataId; - TitleId = info.ProgramId; - UserId = info.UserId; - - var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString); - - InGameList = appData != null; - - if (InGameList) - { - Icon = appData.Icon; - Title = appData.TitleName; - } - else - { - var appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); - Title = appMetadata.Title ?? TitleIdString; - } - - Task.Run(() => - { - var saveRoot = Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}"); - - long totalSize = GetDirectorySize(saveRoot); - - static long GetDirectorySize(string path) - { - long size = 0; - if (Directory.Exists(path)) - { - var directories = Directory.GetDirectories(path); - foreach (var directory in directories) - { - size += GetDirectorySize(directory); - } - - var files = Directory.GetFiles(path); - foreach (var file in files) - { - size += new FileInfo(file).Length; - } - } - - return size; - } - - Size = totalSize; - }); - - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs deleted file mode 100644 index 7f04c0ee..00000000 --- a/src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Ryujinx.Ava.UI.Models -{ - internal class StatusUpdatedEventArgs : EventArgs - { - public bool VSyncEnabled { get; } - public string VolumeStatus { get; } - public string GpuBackend { get; } - public string AspectRatio { get; } - public string DockedMode { get; } - public string FifoStatus { get; } - public string GameStatus { get; } - public string GpuName { get; } - - public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) - { - VSyncEnabled = vSyncEnabled; - VolumeStatus = volumeStatus; - GpuBackend = gpuBackend; - DockedMode = dockedMode; - AspectRatio = aspectRatio; - GameStatus = gameStatus; - FifoStatus = fifoStatus; - GpuName = gpuName; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/TempProfile.cs b/src/Ryujinx.Ava/UI/Models/TempProfile.cs deleted file mode 100644 index 659092e6..00000000 --- a/src/Ryujinx.Ava/UI/Models/TempProfile.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System; - -namespace Ryujinx.Ava.UI.Models -{ - public class TempProfile : BaseModel - { - private readonly UserProfile _profile; - private byte[] _image; - private string _name = String.Empty; - private UserId _userId; - - public static uint MaxProfileNameLength => 0x20; - - public byte[] Image - { - get => _image; - set - { - _image = value; - OnPropertyChanged(); - } - } - - public UserId UserId - { - get => _userId; - set - { - _userId = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(UserIdString)); - } - } - - public string UserIdString => _userId.ToString(); - - public string Name - { - get => _name; - set - { - _name = value; - OnPropertyChanged(); - } - } - - public TempProfile(UserProfile profile) - { - _profile = profile; - - if (_profile != null) - { - Image = profile.Image; - Name = profile.Name; - UserId = profile.UserId; - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/TimeZone.cs b/src/Ryujinx.Ava/UI/Models/TimeZone.cs deleted file mode 100644 index 950fbce4..00000000 --- a/src/Ryujinx.Ava/UI/Models/TimeZone.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.Ava.UI.Models -{ - internal class TimeZone - { - public TimeZone(string utcDifference, string location, string abbreviation) - { - UtcDifference = utcDifference; - Location = location; - Abbreviation = abbreviation; - } - - public string UtcDifference { get; set; } - public string Location { get; set; } - public string Abbreviation { get; set; } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs b/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs deleted file mode 100644 index c270c9ed..00000000 --- a/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using LibHac.Ns; -using Ryujinx.Ava.Common.Locale; - -namespace Ryujinx.Ava.UI.Models -{ - public class TitleUpdateModel - { - public ApplicationControlProperty Control { get; } - public string Path { get; } - - public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString()); - - public TitleUpdateModel(ApplicationControlProperty control, string path) - { - Control = control; - Path = path; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Models/UserProfile.cs b/src/Ryujinx.Ava/UI/Models/UserProfile.cs deleted file mode 100644 index 7a9237fe..00000000 --- a/src/Ryujinx.Ava/UI/Models/UserProfile.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Avalonia.Media; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ava.UI.Views.User; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; - -namespace Ryujinx.Ava.UI.Models -{ - public class UserProfile : BaseModel - { - private readonly Profile _profile; - private readonly NavigationDialogHost _owner; - private byte[] _image; - private string _name; - private UserId _userId; - private bool _isPointerOver; - private IBrush _backgroundColor; - - public byte[] Image - { - get => _image; - set - { - _image = value; - OnPropertyChanged(); - } - } - - public UserId UserId - { - get => _userId; - set - { - _userId = value; - OnPropertyChanged(); - } - } - - public string Name - { - get => _name; - set - { - _name = value; - OnPropertyChanged(); - } - } - - public bool IsPointerOver - { - get => _isPointerOver; - set - { - _isPointerOver = value; - OnPropertyChanged(); - } - } - - public IBrush BackgroundColor - { - get => _backgroundColor; - set - { - _backgroundColor = value; - OnPropertyChanged(); - } - } - - public UserProfile(Profile profile, NavigationDialogHost owner) - { - _profile = profile; - _owner = owner; - - UpdateBackground(); - - Image = profile.Image; - Name = profile.Name; - UserId = profile.UserId; - } - - public void UpdateState() - { - UpdateBackground(); - OnPropertyChanged(nameof(Name)); - } - - private void UpdateBackground() - { - var currentApplication = Avalonia.Application.Current; - currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color); - - if (color is not null) - { - BackgroundColor = _profile.AccountState == AccountState.Open ? new SolidColorBrush((Color)color) : Brushes.Transparent; - } - } - - public void Recover(UserProfile userProfile) - { - _owner.Navigate(typeof(UserEditorView), (_owner, userProfile, true)); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs deleted file mode 100644 index 3bf19b43..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs +++ /dev/null @@ -1,294 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Platform; -using Ryujinx.Common.Configuration; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using SPB.Graphics; -using SPB.Platform; -using SPB.Platform.GLX; -using SPB.Platform.X11; -using SPB.Windowing; -using System; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Threading.Tasks; -using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; - -namespace Ryujinx.Ava.UI.Renderer -{ - public class EmbeddedWindow : NativeControlHost - { - private WindowProc _wndProcDelegate; - private string _className; - - protected GLXWindow X11Window { get; set; } - - protected IntPtr WindowHandle { get; set; } - protected IntPtr X11Display { get; set; } - protected IntPtr NsView { get; set; } - protected IntPtr MetalLayer { get; set; } - - public delegate void UpdateBoundsCallbackDelegate(Rect rect); - private UpdateBoundsCallbackDelegate _updateBoundsCallback; - - public event EventHandler<IntPtr> WindowCreated; - public event EventHandler<Size> BoundsChanged; - - public EmbeddedWindow() - { - this.GetObservable(BoundsProperty).Subscribe(StateChanged); - - Initialized += OnNativeEmbeddedWindowCreated; - } - - public virtual void OnWindowCreated() { } - - protected virtual void OnWindowDestroyed() { } - - protected virtual void OnWindowDestroying() - { - WindowHandle = IntPtr.Zero; - X11Display = IntPtr.Zero; - NsView = IntPtr.Zero; - MetalLayer = IntPtr.Zero; - } - - private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e) - { - OnWindowCreated(); - - Task.Run(() => - { - WindowCreated?.Invoke(this, WindowHandle); - }); - } - - private void StateChanged(Rect rect) - { - BoundsChanged?.Invoke(this, rect.Size); - _updateBoundsCallback?.Invoke(rect); - } - - protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control) - { - if (OperatingSystem.IsLinux()) - { - return CreateLinux(control); - } - - if (OperatingSystem.IsWindows()) - { - return CreateWin32(control); - } - - if (OperatingSystem.IsMacOS()) - { - return CreateMacOS(); - } - - return base.CreateNativeControlCore(control); - } - - protected override void DestroyNativeControlCore(IPlatformHandle control) - { - OnWindowDestroying(); - - if (OperatingSystem.IsLinux()) - { - DestroyLinux(); - } - else if (OperatingSystem.IsWindows()) - { - DestroyWin32(control); - } - else if (OperatingSystem.IsMacOS()) - { - DestroyMacOS(); - } - else - { - base.DestroyNativeControlCore(control); - } - - OnWindowDestroyed(); - } - - [SupportedOSPlatform("linux")] - private IPlatformHandle CreateLinux(IPlatformHandle control) - { - if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) - { - X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle)); - X11Window.Hide(); - } - else - { - X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow; - } - - WindowHandle = X11Window.WindowHandle.RawHandle; - X11Display = X11Window.DisplayHandle.RawHandle; - - return new PlatformHandle(WindowHandle, "X11"); - } - - [SupportedOSPlatform("windows")] - IPlatformHandle CreateWin32(IPlatformHandle control) - { - _className = "NativeWindow-" + Guid.NewGuid(); - - _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) - { - if (VisualRoot != null) - { - if (msg == WindowsMessages.Lbuttondown || - msg == WindowsMessages.Rbuttondown || - msg == WindowsMessages.Lbuttonup || - msg == WindowsMessages.Rbuttonup || - msg == WindowsMessages.Mousemove) - { - Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value; - Pointer pointer = new(0, PointerType.Mouse, true); - -#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete) - switch (msg) - { - case WindowsMessages.Lbuttondown: - case WindowsMessages.Rbuttondown: - { - bool isLeft = msg == WindowsMessages.Lbuttondown; - RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; - PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed); - - var evnt = new PointerPressedEventArgs( - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - properties, - KeyModifiers.None); - - RaiseEvent(evnt); - - break; - } - case WindowsMessages.Lbuttonup: - case WindowsMessages.Rbuttonup: - { - bool isLeft = msg == WindowsMessages.Lbuttonup; - RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; - PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased); - - var evnt = new PointerReleasedEventArgs( - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - properties, - KeyModifiers.None, - isLeft ? MouseButton.Left : MouseButton.Right); - - RaiseEvent(evnt); - - break; - } - case WindowsMessages.Mousemove: - { - var evnt = new PointerEventArgs( - PointerMovedEvent, - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other), - KeyModifiers.None); - - RaiseEvent(evnt); - - break; - } - } -#pragma warning restore CS0618 - } - } - - return DefWindowProc(hWnd, msg, wParam, lParam); - }; - - WndClassEx wndClassEx = new() - { - cbSize = Marshal.SizeOf<WndClassEx>(), - hInstance = GetModuleHandle(null), - lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), - style = ClassStyles.CsOwndc, - lpszClassName = Marshal.StringToHGlobalUni(_className), - hCursor = CreateArrowCursor(), - }; - - RegisterClassEx(ref wndClassEx); - - WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - - Marshal.FreeHGlobal(wndClassEx.lpszClassName); - - return new PlatformHandle(WindowHandle, "HWND"); - } - - [SupportedOSPlatform("macos")] - IPlatformHandle CreateMacOS() - { - // Create a new CAMetalLayer. - ObjectiveC.Object layerObject = new("CAMetalLayer"); - ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc"); - metalLayer.SendMessage("init"); - - // Create a child NSView to render into. - ObjectiveC.Object nsViewObject = new("NSView"); - ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc"); - child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0)); - - // Make its renderer our metal layer. - child.SendMessage("setWantsLayer:", 1); - child.SendMessage("setLayer:", metalLayer); - metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); - - // Ensure the scale factor is up to date. - _updateBoundsCallback = rect => - { - metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); - }; - - IntPtr nsView = child.ObjPtr; - MetalLayer = metalLayer.ObjPtr; - NsView = nsView; - - return new PlatformHandle(nsView, "NSView"); - } - - [SupportedOSPlatform("Linux")] - void DestroyLinux() - { - X11Window?.Dispose(); - } - - [SupportedOSPlatform("windows")] - void DestroyWin32(IPlatformHandle handle) - { - DestroyWindow(handle.Handle); - UnregisterClass(_className, GetModuleHandle(null)); - } - - [SupportedOSPlatform("macos")] -#pragma warning disable CA1822 // Mark member as static - void DestroyMacOS() - { - // TODO - } -#pragma warning restore CA1822 - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs deleted file mode 100644 index 3842301d..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs +++ /dev/null @@ -1,94 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.OpenGL; -using Ryujinx.UI.Common.Configuration; -using SPB.Graphics; -using SPB.Graphics.Exceptions; -using SPB.Graphics.OpenGL; -using SPB.Platform; -using SPB.Platform.WGL; -using SPB.Windowing; -using System; - -namespace Ryujinx.Ava.UI.Renderer -{ - public class EmbeddedWindowOpenGL : EmbeddedWindow - { - private SwappableNativeWindowBase _window; - - public OpenGLContextBase Context { get; set; } - - protected override void OnWindowDestroying() - { - Context.Dispose(); - - base.OnWindowDestroying(); - } - - public override void OnWindowCreated() - { - base.OnWindowCreated(); - - if (OperatingSystem.IsWindows()) - { - _window = new WGLWindow(new NativeHandle(WindowHandle)); - } - else if (OperatingSystem.IsLinux()) - { - _window = X11Window; - } - else - { - throw new PlatformNotSupportedException(); - } - - var flags = OpenGLContextFlags.Compat; - if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None) - { - flags |= OpenGLContextFlags.Debug; - } - - var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; - - Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags); - - Context.Initialize(_window); - Context.MakeCurrent(_window); - - GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress)); - - Context.MakeCurrent(null); - } - - public void MakeCurrent(bool unbind = false, bool shouldThrow = true) - { - try - { - Context?.MakeCurrent(!unbind ? _window : null); - } - catch (ContextException e) - { - if (shouldThrow) - { - throw; - } - - Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}"); - } - } - - public void SwapBuffers() - { - _window?.SwapBuffers(); - } - - public void InitializeBackgroundContext(IRenderer renderer) - { - (renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context)); - - MakeCurrent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs deleted file mode 100644 index fafbec20..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Silk.NET.Vulkan; -using SPB.Graphics.Vulkan; -using SPB.Platform.Metal; -using SPB.Platform.Win32; -using SPB.Platform.X11; -using SPB.Windowing; -using System; - -namespace Ryujinx.Ava.UI.Renderer -{ - public class EmbeddedWindowVulkan : EmbeddedWindow - { - public SurfaceKHR CreateSurface(Instance instance) - { - NativeWindowBase nativeWindowBase; - - if (OperatingSystem.IsWindows()) - { - nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle)); - } - else if (OperatingSystem.IsLinux()) - { - nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle)); - } - else if (OperatingSystem.IsMacOS()) - { - nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer)); - } - else - { - throw new PlatformNotSupportedException(); - } - - return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase)); - } - - public SurfaceKHR CreateSurface(Instance instance, Vk _) - { - return CreateSurface(instance); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs b/src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs deleted file mode 100644 index 85e8585f..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -using OpenTK; -using System; - -namespace Ryujinx.Ava.UI.Renderer -{ - internal class OpenTKBindingsContext : IBindingsContext - { - private readonly Func<string, IntPtr> _getProcAddress; - - public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress) - { - _getProcAddress = getProcAddress; - } - - public IntPtr GetProcAddress(string procName) - { - return _getProcAddress(procName); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml deleted file mode 100644 index e0b586b4..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml +++ /dev/null @@ -1,12 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - d:DesignWidth="800" - d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Renderer.RendererHost" - FlowDirection="LeftToRight" - Focusable="True"> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs deleted file mode 100644 index d055d9ea..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Ryujinx.Common.Configuration; -using Ryujinx.UI.Common.Configuration; -using System; - -namespace Ryujinx.Ava.UI.Renderer -{ - public partial class RendererHost : UserControl, IDisposable - { - public readonly EmbeddedWindow EmbeddedWindow; - - public event EventHandler<EventArgs> WindowCreated; - public event Action<object, Size> BoundsChanged; - - public RendererHost() - { - InitializeComponent(); - - if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) - { - EmbeddedWindow = new EmbeddedWindowOpenGL(); - } - else - { - EmbeddedWindow = new EmbeddedWindowVulkan(); - } - - Initialize(); - } - - private void Initialize() - { - EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated; - EmbeddedWindow.BoundsChanged += CurrentWindow_BoundsChanged; - - Content = EmbeddedWindow; - } - - public void Dispose() - { - if (EmbeddedWindow != null) - { - EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated; - EmbeddedWindow.BoundsChanged -= CurrentWindow_BoundsChanged; - } - - GC.SuppressFinalize(this); - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - - Dispose(); - } - - private void CurrentWindow_BoundsChanged(object sender, Size e) - { - BoundsChanged?.Invoke(sender, e); - } - - private void CurrentWindow_WindowCreated(object sender, IntPtr e) - { - WindowCreated?.Invoke(this, EventArgs.Empty); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs b/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs deleted file mode 100644 index 63bf6cf7..00000000 --- a/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs +++ /dev/null @@ -1,49 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.OpenGL; -using SPB.Graphics; -using SPB.Graphics.OpenGL; -using SPB.Platform; -using SPB.Windowing; - -namespace Ryujinx.Ava.UI.Renderer -{ - class SPBOpenGLContext : IOpenGLContext - { - private readonly OpenGLContextBase _context; - private readonly NativeWindowBase _window; - - private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) - { - _context = context; - _window = window; - } - - public void Dispose() - { - _context.Dispose(); - _window.Dispose(); - } - - public void MakeCurrent() - { - _context.MakeCurrent(_window); - } - - public bool HasContext() => _context.IsCurrent; - - public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) - { - OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); - NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); - - context.Initialize(window); - context.MakeCurrent(window); - - GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress)); - - context.MakeCurrent(null); - - return new SPBOpenGLContext(context, window); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs deleted file mode 100644 index 6020f40e..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Avalonia.Media.Imaging; -using Avalonia.Platform; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Configuration; -using System; -using System.Net.Http; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class AboutWindowViewModel : BaseModel - { - private Bitmap _githubLogo; - private Bitmap _discordLogo; - private Bitmap _patreonLogo; - private Bitmap _twitterLogo; - - private string _version; - private string _supporters; - - public Bitmap GithubLogo - { - get => _githubLogo; - set - { - _githubLogo = value; - OnPropertyChanged(); - } - } - - public Bitmap DiscordLogo - { - get => _discordLogo; - set - { - _discordLogo = value; - OnPropertyChanged(); - } - } - - public Bitmap PatreonLogo - { - get => _patreonLogo; - set - { - _patreonLogo = value; - OnPropertyChanged(); - } - } - - public Bitmap TwitterLogo - { - get => _twitterLogo; - set - { - _twitterLogo = value; - OnPropertyChanged(); - } - } - - public string Supporters - { - get => _supporters; - set - { - _supporters = value; - OnPropertyChanged(); - } - } - - public string Version - { - get => _version; - set - { - _version = value; - OnPropertyChanged(); - } - } - - public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); - - public AboutWindowViewModel() - { - Version = Program.Version; - - 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"))); - } - 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"))); - } - - Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson); - } - - private async Task DownloadPatronsJson() - { - if (!NetworkInterface.GetIsNetworkAvailable()) - { - Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError]; - - return; - } - - HttpClient httpClient = new(); - - try - { - string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); - - Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n"; - } - catch - { - Supporters = LocaleManager.Instance[LocaleKeys.ApiError]; - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs deleted file mode 100644 index 8f09568a..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs +++ /dev/null @@ -1,518 +0,0 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Media.Imaging; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Models.Amiibo; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class AmiiboWindowViewModel : BaseModel, IDisposable - { - private const string DefaultJson = "{ \"amiibo\": [] }"; - private const float AmiiboImageSize = 350f; - - private readonly string _amiiboJsonPath; - private readonly byte[] _amiiboLogoBytes; - private readonly HttpClient _httpClient; - private readonly StyleableWindow _owner; - - private Bitmap _amiiboImage; - private List<AmiiboApi> _amiiboList; - private AvaloniaList<AmiiboApi> _amiibos; - private ObservableCollection<string> _amiiboSeries; - - private int _amiiboSelectedIndex; - private int _seriesSelectedIndex; - private bool _enableScanning; - private bool _showAllAmiibo; - private bool _useRandomUuid; - private string _usage; - - private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId) - { - _owner = owner; - - _httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(30), - }; - - LastScannedAmiiboId = lastScannedAmiiboId; - TitleId = titleId; - - Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); - - _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); - _amiiboList = new List<AmiiboApi>(); - _amiiboSeries = new ObservableCollection<string>(); - _amiibos = new AvaloniaList<AmiiboApi>(); - - _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); - - _ = LoadContentAsync(); - } - - public AmiiboWindowViewModel() { } - - public string TitleId { get; set; } - public string LastScannedAmiiboId { get; set; } - - public UserResult Response { get; private set; } - - public bool UseRandomUuid - { - get => _useRandomUuid; - set - { - _useRandomUuid = value; - - OnPropertyChanged(); - } - } - - public bool ShowAllAmiibo - { - get => _showAllAmiibo; - set - { - _showAllAmiibo = value; - - ParseAmiiboData(); - - OnPropertyChanged(); - } - } - - public AvaloniaList<AmiiboApi> AmiiboList - { - get => _amiibos; - set - { - _amiibos = value; - - OnPropertyChanged(); - } - } - - public ObservableCollection<string> AmiiboSeries - { - get => _amiiboSeries; - set - { - _amiiboSeries = value; - OnPropertyChanged(); - } - } - - public int SeriesSelectedIndex - { - get => _seriesSelectedIndex; - set - { - _seriesSelectedIndex = value; - - FilterAmiibo(); - - OnPropertyChanged(); - } - } - - public int AmiiboSelectedIndex - { - get => _amiiboSelectedIndex; - set - { - _amiiboSelectedIndex = value; - - EnableScanning = _amiiboSelectedIndex >= 0 && _amiiboSelectedIndex < _amiibos.Count; - - SetAmiiboDetails(); - - OnPropertyChanged(); - } - } - - public Bitmap AmiiboImage - { - get => _amiiboImage; - set - { - _amiiboImage = value; - - OnPropertyChanged(); - } - } - - public string Usage - { - get => _usage; - set - { - _usage = value; - - OnPropertyChanged(); - } - } - - public bool EnableScanning - { - get => _enableScanning; - set - { - _enableScanning = value; - - OnPropertyChanged(); - } - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _httpClient.Dispose(); - } - - private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) - { - if (string.IsNullOrEmpty(json)) - { - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); - - return false; - } - - try - { - amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); - - return true; - } - catch (JsonException exception) - { - Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); - - return false; - } - } - - private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson() - { - bool localIsValid = false; - bool remoteIsValid = false; - AmiiboJson amiiboJson = new(); - - try - { - try - { - if (File.Exists(_amiiboJsonPath)) - { - localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); - } - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); - } - - if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) - { - remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); - } - } - catch (Exception exception) - { - if (!(localIsValid || remoteIsValid)) - { - Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); - - // Neither local or remote files are valid JSON, close window. - ShowInfoDialog(); - Close(); - } - else if (!remoteIsValid) - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); - - // Only the local file is valid, the local one should be used - // but the user should be warned. - ShowInfoDialog(); - } - } - - return amiiboJson; - } - - private async Task LoadContentAsync() - { - AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); - - _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); - - ParseAmiiboData(); - } - - private void ParseAmiiboData() - { - _amiiboSeries.Clear(); - _amiibos.Clear(); - - for (int i = 0; i < _amiiboList.Count; i++) - { - if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries)) - { - if (!ShowAllAmiibo) - { - foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) - { - if (game != null) - { - if (game.GameId.Contains(TitleId)) - { - AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); - - break; - } - } - } - } - else - { - AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); - } - } - } - - if (LastScannedAmiiboId != "") - { - SelectLastScannedAmiibo(); - } - else - { - SeriesSelectedIndex = 0; - } - } - - private void SelectLastScannedAmiibo() - { - AmiiboApi scanned = _amiiboList.Find(amiibo => amiibo.GetId() == LastScannedAmiiboId); - - SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries); - AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); - } - - private void FilterAmiibo() - { - _amiibos.Clear(); - - if (_seriesSelectedIndex < 0) - { - return; - } - - List<AmiiboApi> amiiboSortedList = _amiiboList - .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) - .OrderBy(amiibo => amiibo.Name).ToList(); - - for (int i = 0; i < amiiboSortedList.Count; i++) - { - if (!_amiibos.Contains(amiiboSortedList[i])) - { - if (!_showAllAmiibo) - { - foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) - { - if (game != null) - { - if (game.GameId.Contains(TitleId)) - { - _amiibos.Add(amiiboSortedList[i]); - - break; - } - } - } - } - else - { - _amiibos.Add(amiiboSortedList[i]); - } - } - } - - AmiiboSelectedIndex = 0; - } - - private void SetAmiiboDetails() - { - ResetAmiiboPreview(); - - Usage = string.Empty; - - if (_amiiboSelectedIndex < 0) - { - return; - } - - AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; - - string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image; - - StringBuilder usageStringBuilder = new(); - - for (int i = 0; i < _amiiboList.Count; i++) - { - if (_amiiboList[i].Equals(selected)) - { - bool writable = false; - - foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) - { - if (item.GameId.Contains(TitleId)) - { - foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) - { - usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); - - writable = usageItem.Write; - } - } - } - - if (usageStringBuilder.Length == 0) - { - usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}."); - } - - Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}"; - } - } - - _ = UpdateAmiiboPreview(imageUrl); - } - - private async Task<bool> NeedsUpdate(DateTime oldLastModified) - { - try - { - HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); - - if (response.IsSuccessStatusCode) - { - return response.Content.Headers.LastModified != oldLastModified; - } - } - catch (HttpRequestException exception) - { - Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); - } - - return false; - } - - private async Task<string> DownloadAmiiboJson() - { - try - { - HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - - if (response.IsSuccessStatusCode) - { - string amiiboJsonString = await response.Content.ReadAsStringAsync(); - - try - { - using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); - dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); - } - - return amiiboJsonString; - } - - Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); - } - catch (HttpRequestException exception) - { - Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); - } - - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], - LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], - LocaleManager.Instance[LocaleKeys.InputDialogOk], - "", - LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - - return null; - } - - private void Close() - { - Dispatcher.UIThread.Post(_owner.Close); - } - - private async Task UpdateAmiiboPreview(string imageUrl) - { - HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); - - if (response.IsSuccessStatusCode) - { - byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); - using MemoryStream memoryStream = new(amiiboPreviewBytes); - - Bitmap bitmap = new(memoryStream); - - double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width, - AmiiboImageSize / bitmap.Size.Height); - - int resizeHeight = (int)(bitmap.Size.Height * ratio); - int resizeWidth = (int)(bitmap.Size.Width * ratio); - - AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight)); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}"); - } - } - - private void ResetAmiiboPreview() - { - using MemoryStream memoryStream = new(_amiiboLogoBytes); - - Bitmap bitmap = new(memoryStream); - - AmiiboImage = bitmap; - } - - private static async void ShowInfoDialog() - { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], - LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage], - LocaleManager.Instance[LocaleKeys.InputDialogOk], - "", - LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs b/src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs deleted file mode 100644 index 4db9cf81..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class BaseModel : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - protected void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs deleted file mode 100644 index 71ad2c12..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs +++ /dev/null @@ -1,897 +0,0 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Svg.Skia; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.Views.Input; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.Input; -using Ryujinx.UI.Common.Configuration; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Text.Json; -using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; -using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; -using Key = Ryujinx.Common.Configuration.Hid.Key; - -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 KeyboardString = "keyboard"; - private const string ControllerString = "controller"; - private readonly MainWindow _mainWindow; - - private PlayerIndex _playerId; - private int _controller; - private int _controllerNumber; - private string _controllerImage; - private int _device; - private object _configuration; - private string _profileName; - private bool _isLoaded; - - private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public IGamepadDriver AvaloniaKeyboardDriver { get; } - public IGamepad SelectedGamepad { get; private set; } - - public ObservableCollection<PlayerModel> PlayerIndexes { get; set; } - public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } - internal ObservableCollection<ControllerModel> Controllers { get; set; } - public AvaloniaList<string> ProfilesList { get; set; } - public AvaloniaList<string> DeviceList { get; set; } - - // XAML Flags - public bool ShowSettings => _device > 0; - public bool IsController => _device > 1; - public bool IsKeyboard => !IsController; - public bool IsRight { get; set; } - public bool IsLeft { get; set; } - - public bool IsModified { get; set; } - - public object Configuration - { - get => _configuration; - set - { - _configuration = value; - - OnPropertyChanged(); - } - } - - public PlayerIndex PlayerId - { - get => _playerId; - set - { - if (IsModified) - { - return; - } - - IsModified = false; - _playerId = value; - - if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) - { - _playerId = PlayerIndex.Player1; - } - - LoadConfiguration(); - LoadDevice(); - LoadProfiles(); - - _isLoaded = true; - - OnPropertyChanged(); - } - } - - public int Controller - { - get => _controller; - set - { - _controller = value; - - if (_controller == -1) - { - _controller = 0; - } - - if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1) - { - ControllerType controller = Controllers[_controller].Type; - - IsLeft = true; - IsRight = true; - - switch (controller) - { - case ControllerType.Handheld: - ControllerImage = JoyConPairResource; - break; - case ControllerType.ProController: - ControllerImage = ProControllerResource; - break; - case ControllerType.JoyconPair: - ControllerImage = JoyConPairResource; - break; - case ControllerType.JoyconLeft: - ControllerImage = JoyConLeftResource; - IsRight = false; - break; - case ControllerType.JoyconRight: - ControllerImage = JoyConRightResource; - IsLeft = false; - break; - } - - LoadInputDriver(); - LoadProfiles(); - } - - OnPropertyChanged(); - NotifyChanges(); - } - } - - public string ControllerImage - { - get => _controllerImage; - set - { - _controllerImage = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(Image)); - } - } - - public SvgImage Image - { - get - { - SvgImage image = new(); - - if (!string.IsNullOrWhiteSpace(_controllerImage)) - { - SvgSource source = new(default(Uri)); - - source.Load(EmbeddedResources.GetStream(_controllerImage)); - - image.Source = source; - } - - return image; - } - } - - public string ProfileName - { - get => _profileName; set - { - _profileName = value; - - OnPropertyChanged(); - } - } - - public int Device - { - get => _device; - set - { - _device = value < 0 ? 0 : value; - - if (_device >= Devices.Count) - { - return; - } - - var selected = Devices[_device].Type; - - if (selected != DeviceType.None) - { - LoadControllers(); - - if (_isLoaded) - { - LoadConfiguration(LoadDefaultConfiguration()); - } - } - - OnPropertyChanged(); - NotifyChanges(); - } - } - - public InputConfig Config { get; set; } - - public ControllerInputViewModel(UserControl owner) : this() - { - if (Program.PreviewerDetached) - { - _mainWindow = - (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current - .ApplicationLifetime).MainWindow; - - AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); - - _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; - _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; - - _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); - - _isLoaded = false; - - LoadDevices(); - - PlayerId = PlayerIndex.Player1; - } - } - - public ControllerInputViewModel() - { - PlayerIndexes = new ObservableCollection<PlayerModel>(); - Controllers = new ObservableCollection<ControllerModel>(); - Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); - ProfilesList = new AvaloniaList<string>(); - DeviceList = new AvaloniaList<string>(); - - ControllerImage = ProControllerResource; - - PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); - PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); - PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); - PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); - PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); - PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); - PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); - PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); - PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); - } - - private void LoadConfiguration(InputConfig inputConfig = null) - { - Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); - - if (Config is StandardKeyboardInputConfig keyboardInputConfig) - { - Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig); - } - - if (Config is StandardControllerInputConfig controllerInputConfig) - { - Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig); - } - } - - public void LoadDevice() - { - if (Config == null || Config.Backend == InputBackendType.Invalid) - { - Device = 0; - } - else - { - var type = DeviceType.None; - - if (Config is StandardKeyboardInputConfig) - { - type = DeviceType.Keyboard; - } - - if (Config is StandardControllerInputConfig) - { - type = DeviceType.Controller; - } - - var item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); - if (item != default) - { - Device = Devices.ToList().FindIndex(x => x.Id == item.Id); - } - else - { - Device = 0; - } - } - } - - public async void ShowMotionConfig() - { - await MotionInputView.Show(this); - } - - public async void ShowRumbleConfig() - { - await RumbleInputView.Show(this); - } - - private void LoadInputDriver() - { - if (_device < 0) - { - return; - } - - string id = GetCurrentGamepadId(); - var type = Devices[Device].Type; - - if (type == DeviceType.None) - { - return; - } - - if (type == DeviceType.Keyboard) - { - if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) - { - // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... - SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); - } - else - { - SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - } - } - else - { - SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - } - } - - private void HandleOnGamepadDisconnected(string id) - { - Dispatcher.UIThread.Post(() => - { - LoadDevices(); - }); - } - - private void HandleOnGamepadConnected(string id) - { - Dispatcher.UIThread.Post(() => - { - LoadDevices(); - }); - } - - private string GetCurrentGamepadId() - { - if (_device < 0) - { - return string.Empty; - } - - var device = Devices[Device]; - - if (device.Type == DeviceType.None) - { - return null; - } - - return device.Id.Split(" ")[0]; - } - - public void LoadControllers() - { - Controllers.Clear(); - - if (_playerId == PlayerIndex.Handheld) - { - Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); - - Controller = 0; - } - else - { - Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); - Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); - Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); - Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); - - if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) - { - Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); - } - else - { - Controller = 0; - } - } - } - - private static string GetShortGamepadName(string str) - { - const string Ellipsis = "..."; - const int MaxSize = 50; - - if (str.Length > MaxSize) - { - return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; - } - - return str; - } - - private static string GetShortGamepadId(string str) - { - const string Hyphen = "-"; - const int Offset = 1; - - return str[(str.IndexOf(Hyphen) + Offset)..]; - } - - public void LoadDevices() - { - lock (Devices) - { - Devices.Clear(); - DeviceList.Clear(); - Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); - - foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) - { - using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - - if (gamepad != null) - { - Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); - } - } - - foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) - { - using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - - if (gamepad != null) - { - if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) - { - _controllerNumber++; - } - - Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); - } - } - - _controllerNumber = 0; - - DeviceList.AddRange(Devices.Select(x => x.Name)); - Device = Math.Min(Device, DeviceList.Count); - } - } - - private string GetProfileBasePath() - { - string path = AppDataManager.ProfilesDirPath; - var type = Devices[Device == -1 ? 0 : Device].Type; - - if (type == DeviceType.Keyboard) - { - path = Path.Combine(path, KeyboardString); - } - else if (type == DeviceType.Controller) - { - path = Path.Combine(path, ControllerString); - } - - return path; - } - - private void LoadProfiles() - { - ProfilesList.Clear(); - - string basePath = GetProfileBasePath(); - - if (!Directory.Exists(basePath)) - { - Directory.CreateDirectory(basePath); - } - - ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); - - foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) - { - ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); - } - - if (string.IsNullOrWhiteSpace(ProfileName)) - { - ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; - } - } - - public InputConfig LoadDefaultConfiguration() - { - var activeDevice = Devices.FirstOrDefault(); - - if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) - { - activeDevice = Devices[Device]; - } - - InputConfig config; - if (activeDevice.Type == DeviceType.Keyboard) - { - string id = activeDevice.Id; - - config = new StandardKeyboardInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.WindowKeyboard, - Id = id, - ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig<Key> - { - 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<Key> - { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, - }, - RightJoycon = new RightJoyconCommonConfig<Key> - { - 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<Key> - { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, - }, - }; - } - else if (activeDevice.Type == DeviceType.Controller) - { - bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); - - string id = activeDevice.Id.Split(" ")[0]; - - config = new StandardControllerInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.GamepadSDL2, - Id = id, - ControllerType = ControllerType.ProController, - DeadzoneLeft = 0.1f, - DeadzoneRight = 0.1f, - RangeLeft = 1.0f, - RangeRight = 1.0f, - TriggerThreshold = 0.5f, - LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> - { - DpadUp = ConfigGamepadInputId.DpadUp, - DpadDown = ConfigGamepadInputId.DpadDown, - DpadLeft = ConfigGamepadInputId.DpadLeft, - DpadRight = ConfigGamepadInputId.DpadRight, - ButtonMinus = ConfigGamepadInputId.Minus, - ButtonL = ConfigGamepadInputId.LeftShoulder, - ButtonZl = ConfigGamepadInputId.LeftTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - Joystick = ConfigStickInputId.Left, - StickButton = ConfigGamepadInputId.LeftStick, - InvertStickX = false, - InvertStickY = false, - }, - RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> - { - ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, - ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, - ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, - ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, - ButtonPlus = ConfigGamepadInputId.Plus, - ButtonR = ConfigGamepadInputId.RightShoulder, - ButtonZr = ConfigGamepadInputId.RightTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - Joystick = ConfigStickInputId.Right, - StickButton = ConfigGamepadInputId.RightStick, - InvertStickX = false, - InvertStickY = false, - }, - Motion = new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - EnableMotion = true, - Sensitivity = 100, - GyroDeadzone = 1, - }, - Rumble = new RumbleConfigController - { - StrongRumble = 1f, - WeakRumble = 1f, - EnableRumble = false, - }, - }; - } - else - { - config = new InputConfig(); - } - - config.PlayerIndex = _playerId; - - return config; - } - - public async void LoadProfile() - { - if (Device == 0) - { - return; - } - - InputConfig config = null; - - if (string.IsNullOrWhiteSpace(ProfileName)) - { - return; - } - - if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) - { - config = LoadDefaultConfiguration(); - } - else - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - if (!File.Exists(path)) - { - var index = ProfilesList.IndexOf(ProfileName); - if (index != -1) - { - ProfilesList.RemoveAt(index); - } - return; - } - - try - { - config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); - } - catch (JsonException) { } - catch (InvalidOperationException) - { - Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); - - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); - - return; - } - } - - if (config != null) - { - _isLoaded = false; - - LoadConfiguration(config); - - LoadDevice(); - - _isLoaded = true; - - NotifyChanges(); - } - } - - public async void SaveProfile() - { - if (Device == 0) - { - return; - } - - if (Configuration == null) - { - return; - } - - if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); - - return; - } - - bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; - - if (validFileName) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - InputConfig config = null; - - if (IsKeyboard) - { - config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig(); - } - else if (IsController) - { - config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig(); - } - - config.ControllerType = Controllers[_controller].Type; - - string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); - - await File.WriteAllTextAsync(path, jsonString); - - LoadProfiles(); - } - else - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); - } - } - - public async void RemoveProfile() - { - if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) - { - return; - } - - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], - LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - if (File.Exists(path)) - { - File.Delete(path); - } - - LoadProfiles(); - } - } - - public void Save() - { - IsModified = false; - - List<InputConfig> newConfig = new(); - - newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); - - newConfig.Remove(newConfig.Find(x => x == null)); - - if (Device == 0) - { - newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); - } - else - { - var device = Devices[Device]; - - if (device.Type == DeviceType.Keyboard) - { - var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>; - inputConfig.Id = device.Id; - } - else - { - var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>; - inputConfig.Id = device.Id.Split(" ")[0]; - } - - var config = !IsController - ? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig() - : (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig(); - config.ControllerType = Controllers[_controller].Type; - config.PlayerIndex = _playerId; - - int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); - if (i == -1) - { - newConfig.Add(config); - } - else - { - newConfig[i] = config; - } - } - - _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); - - // Atomically replace and signal input change. - // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. - ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - - public void NotifyChange(string property) - { - OnPropertyChanged(property); - } - - public void NotifyChanges() - { - OnPropertyChanged(nameof(Configuration)); - OnPropertyChanged(nameof(IsController)); - OnPropertyChanged(nameof(ShowSettings)); - OnPropertyChanged(nameof(IsKeyboard)); - OnPropertyChanged(nameof(IsRight)); - OnPropertyChanged(nameof(IsLeft)); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - - _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; - _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - - _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); - - SelectedGamepad?.Dispose(); - - AvaloniaKeyboardDriver.Dispose(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs deleted file mode 100644 index 2cd714f4..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ /dev/null @@ -1,340 +0,0 @@ -using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Platform.Storage; -using Avalonia.Threading; -using DynamicData; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.FileSystem; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Application = Avalonia.Application; -using Path = System.IO.Path; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class DownloadableContentManagerViewModel : BaseModel - { - private readonly List<DownloadableContentContainer> _downloadableContentContainerList; - private readonly string _downloadableContentJsonPath; - - private readonly VirtualFileSystem _virtualFileSystem; - private AvaloniaList<DownloadableContentModel> _downloadableContents = new(); - private AvaloniaList<DownloadableContentModel> _views = new(); - private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new(); - - private string _search; - private readonly ulong _titleId; - private readonly IStorageProvider _storageProvider; - - private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AvaloniaList<DownloadableContentModel> DownloadableContents - { - get => _downloadableContents; - set - { - _downloadableContents = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(UpdateCount)); - Sort(); - } - } - - public AvaloniaList<DownloadableContentModel> Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents - { - get => _selectedDownloadableContents; - set - { - _selectedDownloadableContents = value; - OnPropertyChanged(); - } - } - - public string Search - { - get => _search; - set - { - _search = value; - OnPropertyChanged(); - Sort(); - } - } - - public string UpdateCount - { - get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); - } - - public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId) - { - _virtualFileSystem = virtualFileSystem; - - _titleId = titleId; - - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - _storageProvider = desktop.MainWindow.StorageProvider; - } - - _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); - - try - { - _downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, _serializerContext.ListDownloadableContentContainer); - } - catch - { - Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize."); - _downloadableContentContainerList = new List<DownloadableContentContainer>(); - } - - LoadDownloadableContents(); - } - - private void LoadDownloadableContents() - { - foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList) - { - if (File.Exists(downloadableContentContainer.ContainerPath)) - { - using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); - - PartitionFileSystem partitionFileSystem = new(); - partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); - - _virtualFileSystem.ImportTickets(partitionFileSystem); - - foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) - { - using UniqueRef<IFile> ncaFile = new(); - - partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); - if (nca != null) - { - var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), - downloadableContentContainer.ContainerPath, - downloadableContentNca.FullPath, - downloadableContentNca.Enabled); - - DownloadableContents.Add(content); - - if (content.Enabled) - { - SelectedDownloadableContents.Add(content); - } - - OnPropertyChanged(nameof(UpdateCount)); - } - } - } - } - - // NOTE: Save the list again to remove leftovers. - Save(); - Sort(); - } - - public void Sort() - { - DownloadableContents.AsObservableChangeSet() - .Filter(Filter) - .Bind(out var view).AsObservableList(); - - _views.Clear(); - _views.AddRange(view); - OnPropertyChanged(nameof(Views)); - } - - private bool Filter(object arg) - { - if (arg is DownloadableContentModel content) - { - return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower()); - } - - return false; - } - - private Nca TryOpenNca(IStorage ncaStorage, string containerPath) - { - try - { - return new Nca(_virtualFileSystem.KeySet, ncaStorage); - } - catch (Exception ex) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadFileErrorMessage], ex.Message, containerPath)); - }); - } - - return null; - } - - public async void Add() - { - var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle], - AllowMultiple = true, - FileTypeFilter = new List<FilePickerFileType> - { - new("NSP") - { - Patterns = new[] { "*.nsp" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" }, - }, - }, - }); - - foreach (var file in result) - { - await AddDownloadableContent(file.Path.LocalPath); - } - } - - private async Task AddDownloadableContent(string path) - { - if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) - { - return; - } - - using FileStream containerFile = File.OpenRead(path); - - PartitionFileSystem partitionFileSystem = new(); - partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); - bool containsDownloadableContent = false; - - _virtualFileSystem.ImportTickets(partitionFileSystem); - - foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path); - if (nca == null) - { - continue; - } - - if (nca.Header.ContentType == NcaContentType.PublicData) - { - if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId) - { - break; - } - - var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true); - DownloadableContents.Add(content); - SelectedDownloadableContents.Add(content); - - OnPropertyChanged(nameof(UpdateCount)); - Sort(); - - containsDownloadableContent = true; - } - } - - if (!containsDownloadableContent) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]); - } - } - - public void Remove(DownloadableContentModel model) - { - DownloadableContents.Remove(model); - OnPropertyChanged(nameof(UpdateCount)); - Sort(); - } - - public void RemoveAll() - { - DownloadableContents.Clear(); - OnPropertyChanged(nameof(UpdateCount)); - Sort(); - } - - public void EnableAll() - { - SelectedDownloadableContents = new(DownloadableContents); - } - - public void DisableAll() - { - SelectedDownloadableContents.Clear(); - } - - public void Save() - { - _downloadableContentContainerList.Clear(); - - DownloadableContentContainer container = default; - - foreach (DownloadableContentModel downloadableContent in DownloadableContents) - { - if (container.ContainerPath != downloadableContent.ContainerPath) - { - if (!string.IsNullOrWhiteSpace(container.ContainerPath)) - { - _downloadableContentContainerList.Add(container); - } - - container = new DownloadableContentContainer - { - ContainerPath = downloadableContent.ContainerPath, - DownloadableContentNcaList = new List<DownloadableContentNca>(), - }; - } - - container.DownloadableContentNcaList.Add(new DownloadableContentNca - { - Enabled = downloadableContent.Enabled, - TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16), - FullPath = downloadableContent.FullPath, - }); - } - - if (!string.IsNullOrWhiteSpace(container.ContainerPath)) - { - _downloadableContentContainerList.Add(container); - } - - JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer); - } - - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs deleted file mode 100644 index 17bd69b1..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ /dev/null @@ -1,1708 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input; -using Avalonia.Media; -using Avalonia.Platform.Storage; -using Avalonia.Threading; -using DynamicData; -using DynamicData.Binding; -using LibHac.Common; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.Models.Generic; -using Ryujinx.Ava.UI.Renderer; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.HLE.HOS.Services.Account.Acc; -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 SixLabors.ImageSharp.PixelFormats; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; -using Key = Ryujinx.Input.Key; -using MissingKeyException = LibHac.Common.Keys.MissingKeyException; -using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class MainWindowViewModel : BaseModel - { - private const int HotKeyPressDelayMs = 500; - - private ObservableCollection<ApplicationData> _applications; - private string _aspectStatusText; - - private string _loadHeading; - private string _cacheLoadStatus; - private string _searchText; - private Timer _searchTimer; - private string _dockedStatusText; - private string _fifoStatusText; - private string _gameStatusText; - private string _volumeStatusText; - private string _gpuStatusText; - private bool _isAmiiboRequested; - private bool _isGameRunning; - private bool _isFullScreen; - private int _progressMaximum; - private int _progressValue; - private long _lastFullscreenToggle = Environment.TickCount64; - private bool _showLoadProgress; - private bool _showMenuAndStatusBar = true; - private bool _showStatusSeparator; - private Brush _progressBarForegroundColor; - private Brush _progressBarBackgroundColor; - private Brush _vsyncColor; - private byte[] _selectedIcon; - private bool _isAppletMenuActive; - private int _statusBarProgressMaximum; - private int _statusBarProgressValue; - private bool _isPaused; - private bool _showContent = true; - private bool _isLoadingIndeterminate = true; - private bool _showAll; - private string _lastScannedAmiiboId; - private bool _statusBarVisible; - private ReadOnlyObservableCollection<ApplicationData> _appsObservableList; - - private string _showUiKey = "F4"; - private string _pauseKey = "F5"; - private string _screenshotKey = "F8"; - private float _volume; - private float _volumeBeforeMute; - private string _backendText; - - private bool _canUpdate = true; - private Cursor _cursor; - private string _title; - private string _currentEmulatedGamePath; - private readonly AutoResetEvent _rendererWaitEvent; - private WindowState _windowState; - private double _windowWidth; - private double _windowHeight; - - private bool _isActive; - - public ApplicationData ListSelectedApplication; - public ApplicationData GridSelectedApplication; - - private string TitleName { get; set; } - internal AppHost AppHost { get; set; } - - public MainWindowViewModel() - { - Applications = new ObservableCollection<ApplicationData>(); - - Applications.ToObservableChangeSet() - .Filter(Filter) - .Sort(GetComparer()) - .Bind(out _appsObservableList).AsObservableList(); - - _rendererWaitEvent = new AutoResetEvent(false); - - if (Program.PreviewerDetached) - { - LoadConfigurableHotKeys(); - - Volume = ConfigurationState.Instance.System.AudioVolume; - } - } - - public void Initialize( - ContentManager contentManager, - IStorageProvider storageProvider, - ApplicationLibrary applicationLibrary, - VirtualFileSystem virtualFileSystem, - AccountManager accountManager, - InputManager inputManager, - UserChannelPersistence userChannelPersistence, - LibHacHorizonManager libHacHorizonManager, - IHostUIHandler uiHandler, - Action<bool> showLoading, - Action<bool> switchToGameControl, - Action<Control> setMainContent, - TopLevel topLevel) - { - ContentManager = contentManager; - StorageProvider = storageProvider; - ApplicationLibrary = applicationLibrary; - VirtualFileSystem = virtualFileSystem; - AccountManager = accountManager; - InputManager = inputManager; - UserChannelPersistence = userChannelPersistence; - LibHacHorizonManager = libHacHorizonManager; - UiHandler = uiHandler; - - ShowLoading = showLoading; - SwitchToGameControl = switchToGameControl; - SetMainContent = setMainContent; - TopLevel = topLevel; - } - - #region Properties - - public string SearchText - { - get => _searchText; - set - { - _searchText = value; - - _searchTimer?.Dispose(); - - _searchTimer = new Timer(TimerCallback, null, 1000, 0); - } - } - - private void TimerCallback(object obj) - { - RefreshView(); - - _searchTimer.Dispose(); - _searchTimer = null; - } - - public bool CanUpdate - { - get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false); - set - { - _canUpdate = value; - OnPropertyChanged(); - } - } - - public Cursor Cursor - { - get => _cursor; - set - { - _cursor = value; - OnPropertyChanged(); - } - } - - public ReadOnlyObservableCollection<ApplicationData> AppsObservableList - { - get => _appsObservableList; - set - { - _appsObservableList = value; - - OnPropertyChanged(); - } - } - - public bool IsPaused - { - get => _isPaused; - set - { - _isPaused = value; - - OnPropertyChanged(); - } - } - - public long LastFullscreenToggle - { - get => _lastFullscreenToggle; - set - { - _lastFullscreenToggle = value; - - OnPropertyChanged(); - } - } - - public bool StatusBarVisible - { - get => _statusBarVisible && EnableNonGameRunningControls; - set - { - _statusBarVisible = value; - - OnPropertyChanged(); - } - } - - public bool EnableNonGameRunningControls => !IsGameRunning; - - public bool ShowFirmwareStatus => !ShowLoadProgress; - - public bool IsGameRunning - { - get => _isGameRunning; - set - { - _isGameRunning = value; - - if (!value) - { - ShowMenuAndStatusBar = false; - } - - OnPropertyChanged(); - OnPropertyChanged(nameof(EnableNonGameRunningControls)); - OnPropertyChanged(nameof(IsAppletMenuActive)); - OnPropertyChanged(nameof(StatusBarVisible)); - OnPropertyChanged(nameof(ShowFirmwareStatus)); - } - } - - public bool IsAmiiboRequested - { - get => _isAmiiboRequested && _isGameRunning; - set - { - _isAmiiboRequested = value; - - OnPropertyChanged(); - } - } - - public bool ShowLoadProgress - { - get => _showLoadProgress; - set - { - _showLoadProgress = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(ShowFirmwareStatus)); - } - } - - public string GameStatusText - { - get => _gameStatusText; - set - { - _gameStatusText = value; - - OnPropertyChanged(); - } - } - - public bool IsFullScreen - { - get => _isFullScreen; - set - { - _isFullScreen = value; - - OnPropertyChanged(); - } - } - - public bool ShowAll - { - get => _showAll; - set - { - _showAll = value; - - OnPropertyChanged(); - } - } - - public string LastScannedAmiiboId - { - get => _lastScannedAmiiboId; - set - { - _lastScannedAmiiboId = value; - - OnPropertyChanged(); - } - } - - public ApplicationData SelectedApplication - { - get - { - return Glyph switch - { - Glyph.List => ListSelectedApplication, - Glyph.Grid => GridSelectedApplication, - _ => null, - }; - } - } - - public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; - - public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; - - public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; - - public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild; - - public string LoadHeading - { - get => _loadHeading; - set - { - _loadHeading = value; - - OnPropertyChanged(); - } - } - - public string CacheLoadStatus - { - get => _cacheLoadStatus; - set - { - _cacheLoadStatus = value; - - OnPropertyChanged(); - } - } - - public Brush ProgressBarBackgroundColor - { - get => _progressBarBackgroundColor; - set - { - _progressBarBackgroundColor = value; - - OnPropertyChanged(); - } - } - - public Brush ProgressBarForegroundColor - { - get => _progressBarForegroundColor; - set - { - _progressBarForegroundColor = value; - - OnPropertyChanged(); - } - } - - public Brush VsyncColor - { - get => _vsyncColor; - set - { - _vsyncColor = value; - - OnPropertyChanged(); - } - } - - public byte[] SelectedIcon - { - get => _selectedIcon; - set - { - _selectedIcon = value; - - OnPropertyChanged(); - } - } - - public int ProgressMaximum - { - get => _progressMaximum; - set - { - _progressMaximum = value; - - OnPropertyChanged(); - } - } - - public int ProgressValue - { - get => _progressValue; - set - { - _progressValue = value; - - OnPropertyChanged(); - } - } - - public int StatusBarProgressMaximum - { - get => _statusBarProgressMaximum; - set - { - _statusBarProgressMaximum = value; - - OnPropertyChanged(); - } - } - - public int StatusBarProgressValue - { - get => _statusBarProgressValue; - set - { - _statusBarProgressValue = value; - - OnPropertyChanged(); - } - } - - public string FifoStatusText - { - get => _fifoStatusText; - set - { - _fifoStatusText = value; - - OnPropertyChanged(); - } - } - - public string GpuNameText - { - get => _gpuStatusText; - set - { - _gpuStatusText = value; - - OnPropertyChanged(); - } - } - - public string BackendText - { - get => _backendText; - set - { - _backendText = value; - - OnPropertyChanged(); - } - } - - public string DockedStatusText - { - get => _dockedStatusText; - set - { - _dockedStatusText = value; - - OnPropertyChanged(); - } - } - - public string AspectRatioStatusText - { - get => _aspectStatusText; - set - { - _aspectStatusText = value; - - OnPropertyChanged(); - } - } - - public string VolumeStatusText - { - get => _volumeStatusText; - set - { - _volumeStatusText = value; - - OnPropertyChanged(); - } - } - - public bool VolumeMuted => _volume == 0; - - public float Volume - { - get => _volume; - set - { - _volume = value; - - if (_isGameRunning) - { - AppHost.Device.SetVolume(_volume); - } - - OnPropertyChanged(nameof(VolumeStatusText)); - OnPropertyChanged(nameof(VolumeMuted)); - OnPropertyChanged(); - } - } - - public float VolumeBeforeMute - { - get => _volumeBeforeMute; - set - { - _volumeBeforeMute = value; - - OnPropertyChanged(); - } - } - - public bool ShowStatusSeparator - { - get => _showStatusSeparator; - set - { - _showStatusSeparator = value; - - OnPropertyChanged(); - } - } - - public bool ShowMenuAndStatusBar - { - get => _showMenuAndStatusBar; - set - { - _showMenuAndStatusBar = value; - - OnPropertyChanged(); - } - } - - public bool IsLoadingIndeterminate - { - get => _isLoadingIndeterminate; - set - { - _isLoadingIndeterminate = value; - - OnPropertyChanged(); - } - } - - public bool IsActive - { - get => _isActive; - set - { - _isActive = value; - - OnPropertyChanged(); - } - } - - - public bool ShowContent - { - get => _showContent; - set - { - _showContent = value; - - OnPropertyChanged(); - } - } - - public bool IsAppletMenuActive - { - get => _isAppletMenuActive && EnableNonGameRunningControls; - set - { - _isAppletMenuActive = value; - - OnPropertyChanged(); - } - } - - public WindowState WindowState - { - get => _windowState; - internal set - { - _windowState = value; - - OnPropertyChanged(); - } - } - - public double WindowWidth - { - get => _windowWidth; - set - { - _windowWidth = value; - - OnPropertyChanged(); - } - } - - public double WindowHeight - { - get => _windowHeight; - set - { - _windowHeight = value; - - OnPropertyChanged(); - } - } - - public bool IsGrid => Glyph == Glyph.Grid; - public bool IsList => Glyph == Glyph.List; - - internal void Sort(bool isAscending) - { - IsAscending = isAscending; - - RefreshView(); - } - - internal void Sort(ApplicationSort sort) - { - SortMode = sort; - - RefreshView(); - } - - public bool StartGamesInFullscreen - { - get => ConfigurationState.Instance.UI.StartFullscreen; - set - { - ConfigurationState.Instance.UI.StartFullscreen.Value = value; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - - OnPropertyChanged(); - } - } - - public bool ShowConsole - { - get => ConfigurationState.Instance.UI.ShowConsole; - set - { - ConfigurationState.Instance.UI.ShowConsole.Value = value; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - - OnPropertyChanged(); - } - } - - public string Title - { - get => _title; - set - { - _title = value; - - OnPropertyChanged(); - } - } - - public bool ShowConsoleVisible - { - get => ConsoleHelper.SetConsoleWindowStateSupported; - } - - public bool ManageFileTypesVisible - { - get => FileAssociationHelper.IsTypeAssociationSupported; - } - - public ObservableCollection<ApplicationData> Applications - { - get => _applications; - set - { - _applications = value; - - OnPropertyChanged(); - } - } - - public Glyph Glyph - { - get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value; - set - { - ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(IsGrid)); - OnPropertyChanged(nameof(IsList)); - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - public bool ShowNames - { - get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set - { - ConfigurationState.Instance.UI.ShowNames.Value = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(GridSizeScale)); - OnPropertyChanged(nameof(GridItemSelectorSize)); - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - internal ApplicationSort SortMode - { - get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value; - private set - { - ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(SortName)); - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - public int ListItemSelectorSize - { - get - { - return ConfigurationState.Instance.UI.GridSize.Value switch - { - 1 => 78, - 2 => 100, - 3 => 120, - 4 => 140, - _ => 16, - }; - } - } - - public int GridItemSelectorSize - { - get - { - return ConfigurationState.Instance.UI.GridSize.Value switch - { - 1 => 120, - 2 => ShowNames ? 210 : 150, - 3 => ShowNames ? 240 : 180, - 4 => ShowNames ? 280 : 220, - _ => 16, - }; - } - } - - public int GridSizeScale - { - get => ConfigurationState.Instance.UI.GridSize; - set - { - ConfigurationState.Instance.UI.GridSize.Value = value; - - if (value < 2) - { - ShowNames = false; - } - - OnPropertyChanged(); - OnPropertyChanged(nameof(IsGridSmall)); - OnPropertyChanged(nameof(IsGridMedium)); - OnPropertyChanged(nameof(IsGridLarge)); - OnPropertyChanged(nameof(IsGridHuge)); - OnPropertyChanged(nameof(ListItemSelectorSize)); - OnPropertyChanged(nameof(GridItemSelectorSize)); - OnPropertyChanged(nameof(ShowNames)); - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - public string SortName - { - get - { - return SortMode switch - { - ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication], - ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper], - ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed], - ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed], - ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension], - ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize], - ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath], - ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], - _ => string.Empty, - }; - } - } - - public bool IsAscending - { - get => ConfigurationState.Instance.UI.IsAscendingOrder; - private set - { - ConfigurationState.Instance.UI.IsAscendingOrder.Value = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(SortMode)); - OnPropertyChanged(nameof(SortName)); - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - public KeyGesture ShowUiKey - { - get => KeyGesture.Parse(_showUiKey); - set - { - _showUiKey = value.ToString(); - - OnPropertyChanged(); - } - } - - public KeyGesture ScreenshotKey - { - get => KeyGesture.Parse(_screenshotKey); - set - { - _screenshotKey = value.ToString(); - - OnPropertyChanged(); - } - } - - public KeyGesture PauseKey - { - get => KeyGesture.Parse(_pauseKey); set - { - _pauseKey = value.ToString(); - - OnPropertyChanged(); - } - } - - public ContentManager ContentManager { get; private set; } - public IStorageProvider StorageProvider { get; private set; } - public ApplicationLibrary ApplicationLibrary { get; private set; } - public VirtualFileSystem VirtualFileSystem { get; private set; } - public AccountManager AccountManager { get; private set; } - public InputManager InputManager { get; private set; } - public UserChannelPersistence UserChannelPersistence { get; private set; } - public Action<bool> ShowLoading { get; private set; } - public Action<bool> SwitchToGameControl { get; private set; } - public Action<Control> SetMainContent { get; private set; } - public TopLevel TopLevel { get; private set; } - public RendererHost RendererHostControl { get; private set; } - public bool IsClosing { get; set; } - public LibHacHorizonManager LibHacHorizonManager { 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; - public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; - public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; - 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; - - #endregion - - #region PrivateMethods - - private IComparer<ApplicationData> GetComparer() - { - return SortMode switch - { -#pragma warning disable IDE0055 // Disable formatting - ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) - : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName), - ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) - : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer), - ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), - ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), - ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) - : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension), - ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize) - : SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize), - ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) - : SortExpressionComparer<ApplicationData>.Descending(app => app.Path), - ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) - : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite), - _ => null, -#pragma warning restore IDE0055 - }; - } - - public void RefreshView() - { - RefreshGrid(); - } - - private void RefreshGrid() - { - Applications.ToObservableChangeSet() - .Filter(Filter) - .Sort(GetComparer()) - .Bind(out _appsObservableList).AsObservableList(); - - OnPropertyChanged(nameof(AppsObservableList)); - } - - private bool Filter(object arg) - { - if (arg is ApplicationData app) - { - return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower()); - } - - return false; - } - - private async Task HandleFirmwareInstallation(string filename) - { - try - { - SystemVersion firmwareVersion = ContentManager.VerifyFirmwarePackage(filename); - - if (firmwareVersion == null) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); - - return; - } - - string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); - string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); - - SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); - if (currentVersion != null) - { - dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); - } - - dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; - - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - dialogTitle, - dialogMessage, - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); - - if (result == UserResult.Yes) - { - Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); - - Thread thread = new(() => - { - Dispatcher.UIThread.InvokeAsync(delegate - { - waitingDialog.Show(); - }); - - try - { - ContentManager.InstallFirmware(filename); - - Dispatcher.UIThread.InvokeAsync(async delegate - { - waitingDialog.Close(); - - string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); - - await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - - Logger.Info?.Print(LogClass.Application, message); - - // Purge Applet Cache. - - DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); - - if (miiEditorCacheFolder.Exists) - { - miiEditorCacheFolder.Delete(true); - } - }); - } - catch (Exception ex) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - waitingDialog.Close(); - - await ContentDialogHelper.CreateErrorDialog(ex.Message); - }); - } - finally - { - RefreshFirmwareStatus(); - } - }) - { - Name = "GUI.FirmwareInstallerThread", - }; - - thread.Start(); - } - } - catch (MissingKeyException ex) - { - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - Logger.Error?.Print(LogClass.Application, ex.ToString()); - - await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); - } - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(ex.Message); - } - } - - private void ProgressHandler<T>(T state, int current, int total) where T : Enum - { - Dispatcher.UIThread.Post((() => - { - ProgressMaximum = total; - ProgressValue = current; - - switch (state) - { - case LoadState ptcState: - CacheLoadStatus = $"{current} / {total}"; - switch (ptcState) - { - case LoadState.Unloaded: - case LoadState.Loading: - LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingPPTC]; - IsLoadingIndeterminate = false; - break; - case LoadState.Loaded: - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); - IsLoadingIndeterminate = true; - CacheLoadStatus = ""; - break; - } - break; - case ShaderCacheLoadingState shaderCacheState: - CacheLoadStatus = $"{current} / {total}"; - switch (shaderCacheState) - { - case ShaderCacheLoadingState.Start: - case ShaderCacheLoadingState.Loading: - LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders]; - IsLoadingIndeterminate = false; - break; - case ShaderCacheLoadingState.Packaging: - LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders]; - IsLoadingIndeterminate = false; - break; - case ShaderCacheLoadingState.Loaded: - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); - IsLoadingIndeterminate = true; - CacheLoadStatus = ""; - break; - } - break; - default: - throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); - } - })); - } - - private void PrepareLoadScreen() - { - using MemoryStream stream = new(SelectedIcon); - using var gameIconBmp = Image.Load<Bgra32>(stream); - - var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>(); - - const float ColorMultiple = 0.5f; - - Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B); - Color progressBgColor = Color.FromRgb( - (byte)(dominantColor.R * ColorMultiple), - (byte)(dominantColor.G * ColorMultiple), - (byte)(dominantColor.B * ColorMultiple)); - - ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); - ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); - } - - private void InitializeGame() - { - RendererHostControl.WindowCreated += RendererHost_Created; - - AppHost.StatusUpdatedEvent += Update_StatusBar; - AppHost.AppExit += AppHost_AppExit; - - _rendererWaitEvent.WaitOne(); - - AppHost?.Start(); - - AppHost?.DisposeContext(); - } - - private async Task HandleRelaunch() - { - if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) - { - UserChannelPersistence.ShouldRestart = false; - - await LoadApplication(_currentEmulatedGamePath); - } - else - { - // Otherwise, clear state. - UserChannelPersistence = new UserChannelPersistence(); - _currentEmulatedGamePath = null; - } - } - - private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) - { - if (ShowMenuAndStatusBar && !ShowLoadProgress) - { - Dispatcher.UIThread.InvokeAsync(() => - { - Application.Current.Styles.TryGetResource(args.VSyncEnabled - ? "VsyncEnabled" - : "VsyncDisabled", - Application.Current.ActualThemeVariant, - out object color); - - if (color is not null) - { - VsyncColor = new SolidColorBrush((Color)color); - } - - DockedStatusText = args.DockedMode; - AspectRatioStatusText = args.AspectRatio; - GameStatusText = args.GameStatus; - VolumeStatusText = args.VolumeStatus; - FifoStatusText = args.FifoStatus; - GpuNameText = args.GpuName; - BackendText = args.GpuBackend; - - ShowStatusSeparator = true; - }); - } - } - - private void RendererHost_Created(object sender, EventArgs e) - { - ShowLoading(false); - - _rendererWaitEvent.Set(); - } - - #endregion - - #region PublicMethods - - public void SetUiProgressHandlers(Switch emulationContext) - { - if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) - { - emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; - emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; - } - - emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; - emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; - } - - public void LoadConfigurableHotKeys() - { - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey)) - { - ShowUiKey = new KeyGesture(showUiKey); - } - - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) - { - ScreenshotKey = new KeyGesture(screenshotKey); - } - - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) - { - PauseKey = new KeyGesture(pauseKey); - } - } - - public void TakeScreenshot() - { - AppHost.ScreenshotRequested = true; - } - - public void HideUi() - { - ShowMenuAndStatusBar = false; - } - - public void ToggleStartGamesInFullscreen() - { - StartGamesInFullscreen = !StartGamesInFullscreen; - } - - public void ToggleShowConsole() - { - ShowConsole = !ShowConsole; - } - - public void SetListMode() - { - Glyph = Glyph.List; - } - - public void SetGridMode() - { - Glyph = Glyph.Grid; - } - - public void SetAspectRatio(AspectRatio aspectRatio) - { - ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; - } - - public async Task InstallFirmwareFromFile() - { - var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - AllowMultiple = false, - FileTypeFilter = new List<FilePickerFileType> - { - new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes]) - { - Patterns = new[] { "*.xci", "*.zip" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, - MimeTypes = new[] { "application/x-nx-xci", "application/zip" }, - }, - new("XCI") - { - Patterns = new[] { "*.xci" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, - MimeTypes = new[] { "application/x-nx-xci" }, - }, - new("ZIP") - { - Patterns = new[] { "*.zip" }, - AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, - MimeTypes = new[] { "application/zip" }, - }, - }, - }); - - if (result.Count > 0) - { - await HandleFirmwareInstallation(result[0].Path.LocalPath); - } - } - - public async Task InstallFirmwareFromFolder() - { - var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - AllowMultiple = false, - }); - - if (result.Count > 0) - { - await HandleFirmwareInstallation(result[0].Path.LocalPath); - } - } - - public void OpenRyujinxFolder() - { - OpenHelper.OpenFolder(AppDataManager.BaseDirPath); - } - - public void OpenLogsFolder() - { - string logPath = AppDataManager.GetOrCreateLogsDir(); - if (!string.IsNullOrEmpty(logPath)) - { - OpenHelper.OpenFolder(logPath); - } - } - - public void ToggleDockMode() - { - if (IsGameRunning) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; - } - } - - public async Task ExitCurrentState() - { - if (WindowState == WindowState.FullScreen) - { - ToggleFullscreen(); - } - else if (IsGameRunning) - { - await Task.Delay(100); - - AppHost?.ShowExitPrompt(); - } - } - - public static void ChangeLanguage(object languageCode) - { - LocaleManager.Instance.LoadLanguage((string)languageCode); - - if (Program.PreviewerDetached) - { - ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode; - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - - public async Task ManageProfiles() - { - await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); - } - - public void SimulateWakeUpMessage() - { - AppHost.Device.System.SimulateWakeUpMessage(); - } - - public async Task OpenFile() - { - var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], - AllowMultiple = false, - FileTypeFilter = new List<FilePickerFileType> - { - new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) - { - Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" }, - AppleUniformTypeIdentifiers = new[] - { - "com.ryujinx.nsp", - "com.ryujinx.xci", - "com.ryujinx.nca", - "com.ryujinx.nro", - "com.ryujinx.nso", - }, - MimeTypes = new[] - { - "application/x-nx-nsp", - "application/x-nx-xci", - "application/x-nx-nca", - "application/x-nx-nro", - "application/x-nx-nso", - }, - }, - new("NSP") - { - Patterns = new[] { "*.nsp" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" }, - }, - new("XCI") - { - Patterns = new[] { "*.xci" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, - MimeTypes = new[] { "application/x-nx-xci" }, - }, - new("NCA") - { - Patterns = new[] { "*.nca" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, - MimeTypes = new[] { "application/x-nx-nca" }, - }, - new("NRO") - { - Patterns = new[] { "*.nro" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, - MimeTypes = new[] { "application/x-nx-nro" }, - }, - new("NSO") - { - Patterns = new[] { "*.nso" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, - MimeTypes = new[] { "application/x-nx-nso" }, - }, - }, - }); - - if (result.Count > 0) - { - await LoadApplication(result[0].Path.LocalPath); - } - } - - public async Task OpenFolder() - { - var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], - AllowMultiple = false, - }); - - if (result.Count > 0) - { - await LoadApplication(result[0].Path.LocalPath); - } - } - - public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "") - { - if (AppHost != null) - { - await ContentDialogHelper.CreateInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage], - LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage], - LocaleManager.Instance[LocaleKeys.InputDialogOk], - "", - LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - - return; - } - -#if RELEASE - await PerformanceCheck(); -#endif - - Logger.RestartTime(); - - SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language); - - PrepareLoadScreen(); - - RendererHostControl = new RendererHost(); - - AppHost = new AppHost( - RendererHostControl, - InputManager, - path, - VirtualFileSystem, - ContentManager, - AccountManager, - UserChannelPersistence, - this, - TopLevel); - - if (!await AppHost.LoadGuestApplication()) - { - AppHost.DisposeContext(); - AppHost = null; - - return; - } - - CanUpdate = false; - - LoadHeading = TitleName = titleName; - - if (string.IsNullOrWhiteSpace(titleName)) - { - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); - TitleName = AppHost.Device.Processes.ActiveApplication.Name; - } - - SwitchToRenderer(startFullscreen); - - _currentEmulatedGamePath = path; - - Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; - gameThread.Start(); - } - - public void SwitchToRenderer(bool startFullscreen) - { - Dispatcher.UIThread.Post(() => - { - SwitchToGameControl(startFullscreen); - - SetMainContent(RendererHostControl); - - RendererHostControl.Focus(); - }); - } - - public static void UpdateGameMetadata(string titleId) - { - ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => - { - appMetadata.UpdatePostGame(); - }); - } - - public void RefreshFirmwareStatus() - { - SystemVersion version = null; - try - { - version = ContentManager.GetCurrentFirmwareVersion(); - } - catch (Exception) { } - - bool hasApplet = false; - - if (version != null) - { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); - - hasApplet = version.Major > 3; - } - else - { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); - } - - IsAppletMenuActive = hasApplet; - } - - public void AppHost_AppExit(object sender, EventArgs e) - { - if (IsClosing) - { - return; - } - - IsGameRunning = false; - - Dispatcher.UIThread.InvokeAsync(async () => - { - ShowMenuAndStatusBar = true; - ShowContent = true; - ShowLoadProgress = false; - IsLoadingIndeterminate = false; - CanUpdate = true; - Cursor = Cursor.Default; - - SetMainContent(null); - - AppHost = null; - - await HandleRelaunch(); - }); - - RendererHostControl.WindowCreated -= RendererHost_Created; - RendererHostControl = null; - - SelectedIcon = null; - - Dispatcher.UIThread.InvokeAsync(() => - { - Title = $"Ryujinx {Program.Version}"; - }); - } - - public void ToggleFullscreen() - { - if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs) - { - return; - } - - LastFullscreenToggle = Environment.TickCount64; - - if (WindowState == WindowState.FullScreen) - { - WindowState = WindowState.Normal; - - if (IsGameRunning) - { - ShowMenuAndStatusBar = true; - } - } - else - { - WindowState = WindowState.FullScreen; - - if (IsGameRunning) - { - ShowMenuAndStatusBar = false; - } - } - - IsFullScreen = WindowState == WindowState.FullScreen; - } - - public static void SaveConfig() - { - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - - public static async Task PerformanceCheck() - { - if (ConfigurationState.Instance.Logger.EnableTrace.Value) - { - string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; - string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; - - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - mainMessage, - secondaryMessage, - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - ConfigurationState.Instance.Logger.EnableTrace.Value = false; - - SaveConfig(); - } - } - - if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) - { - string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; - string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; - - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - mainMessage, - secondaryMessage, - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; - - SaveConfig(); - } - } - } - #endregion - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs deleted file mode 100644 index 8321bf89..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs +++ /dev/null @@ -1,336 +0,0 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Platform.Storage; -using Avalonia.Threading; -using DynamicData; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.HOS; -using System; -using System.IO; -using System.Linq; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class ModManagerViewModel : BaseModel - { - private readonly string _modJsonPath; - - private AvaloniaList<ModModel> _mods = new(); - private AvaloniaList<ModModel> _views = new(); - private AvaloniaList<ModModel> _selectedMods = new(); - - private string _search; - private readonly ulong _applicationId; - private readonly IStorageProvider _storageProvider; - - private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AvaloniaList<ModModel> Mods - { - get => _mods; - set - { - _mods = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(ModCount)); - Sort(); - } - } - - public AvaloniaList<ModModel> Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public AvaloniaList<ModModel> SelectedMods - { - get => _selectedMods; - set - { - _selectedMods = value; - OnPropertyChanged(); - } - } - - public string Search - { - get => _search; - set - { - _search = value; - OnPropertyChanged(); - Sort(); - } - } - - public string ModCount - { - get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); - } - - public ModManagerViewModel(ulong applicationId) - { - _applicationId = applicationId; - - _modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); - - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - _storageProvider = desktop.MainWindow.StorageProvider; - } - - LoadMods(applicationId); - } - - private void LoadMods(ulong applicationId) - { - Mods.Clear(); - SelectedMods.Clear(); - - string[] modsBasePaths = [ModLoader.GetSdModsBasePath(), ModLoader.GetModsBasePath()]; - - foreach (var path in modsBasePaths) - { - var inSd = path == ModLoader.GetSdModsBasePath(); - var modCache = new ModLoader.ModCache(); - - ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId); - - foreach (var mod in modCache.RomfsDirs) - { - var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); - if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) - { - Mods.Add(modModel); - } - } - - foreach (var mod in modCache.RomfsContainers) - { - Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); - } - - foreach (var mod in modCache.ExefsDirs) - { - var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); - if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) - { - Mods.Add(modModel); - } - } - - foreach (var mod in modCache.ExefsContainers) - { - Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); - } - } - - Sort(); - } - - public void Sort() - { - Mods.AsObservableChangeSet() - .Filter(Filter) - .Bind(out var view).AsObservableList(); - - _views.Clear(); - _views.AddRange(view); - - SelectedMods = new(Views.Where(x => x.Enabled)); - - OnPropertyChanged(nameof(ModCount)); - OnPropertyChanged(nameof(Views)); - OnPropertyChanged(nameof(SelectedMods)); - } - - private bool Filter(object arg) - { - if (arg is ModModel content) - { - return string.IsNullOrWhiteSpace(_search) || content.Name.ToLower().Contains(_search.ToLower()); - } - - return false; - } - - public void Save() - { - ModMetadata modData = new(); - - foreach (ModModel mod in Mods) - { - modData.Mods.Add(new Mod - { - Name = mod.Name, - Path = mod.Path, - Enabled = SelectedMods.Contains(mod), - }); - } - - JsonHelper.SerializeToFile(_modJsonPath, modData, _serializerContext.ModMetadata); - } - - public void Delete(ModModel model) - { - var isSubdir = true; - var pathToDelete = model.Path; - var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath(); - var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16")); - - if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir) - { - isSubdir = false; - } - - if (isSubdir) - { - var parentDir = String.Empty; - - foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) - { - if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) - { - parentDir = dir; - break; - } - } - - if (parentDir == String.Empty) - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( - LocaleKeys.DialogModDeleteNoParentMessage, - model.Path)); - }); - return; - } - } - - Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\""); - Directory.Delete(pathToDelete, true); - - Mods.Remove(model); - OnPropertyChanged(nameof(ModCount)); - Sort(); - } - - private void AddMod(DirectoryInfo directory) - { - string[] directories; - - try - { - directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories); - } - catch (Exception exception) - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( - LocaleKeys.DialogLoadFileErrorMessage, - exception.ToString(), - directory)); - }); - return; - } - - var destinationDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); - - // TODO: More robust checking for valid mod folders - var isDirectoryValid = true; - - if (directories.Length == 0) - { - isDirectoryValid = false; - } - - if (!isDirectoryValid) - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogModInvalidMessage]); - }); - return; - } - - foreach (var dir in directories) - { - string dirToCreate = dir.Replace(directory.Parent.ToString(), destinationDir); - - // Mod already exists - if (Directory.Exists(dirToCreate)) - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( - LocaleKeys.DialogLoadFileErrorMessage, - LocaleManager.Instance[LocaleKeys.DialogModAlreadyExistsMessage], - dirToCreate)); - }); - - return; - } - - Directory.CreateDirectory(dirToCreate); - } - - var files = Directory.GetFiles(directory.ToString(), "*", SearchOption.AllDirectories); - - foreach (var file in files) - { - File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true); - } - - LoadMods(_applicationId); - } - - public async void Add() - { - var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.SelectModDialogTitle], - AllowMultiple = true, - }); - - foreach (var folder in result) - { - AddMod(new DirectoryInfo(folder.Path.LocalPath)); - } - } - - public void DeleteAll() - { - foreach (var mod in Mods) - { - Delete(mod); - } - - Mods.Clear(); - OnPropertyChanged(nameof(ModCount)); - Sort(); - } - - public void EnableAll() - { - SelectedMods = new(Mods); - } - - public void DisableAll() - { - SelectedMods.Clear(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs deleted file mode 100644 index 0b12a51f..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace Ryujinx.Ava.UI.ViewModels -{ - public class MotionInputViewModel : BaseModel - { - private int _slot; - public int Slot - { - get => _slot; - set - { - _slot = value; - OnPropertyChanged(); - } - } - - private int _altSlot; - public int AltSlot - { - get => _altSlot; - set - { - _altSlot = value; - OnPropertyChanged(); - } - } - - private string _dsuServerHost; - public string DsuServerHost - { - get => _dsuServerHost; - set - { - _dsuServerHost = value; - OnPropertyChanged(); - } - } - - private int _dsuServerPort; - public int DsuServerPort - { - get => _dsuServerPort; - set - { - _dsuServerPort = value; - OnPropertyChanged(); - } - } - - private bool _mirrorInput; - public bool MirrorInput - { - get => _mirrorInput; - set - { - _mirrorInput = value; - OnPropertyChanged(); - } - } - - private int _sensitivity; - public int Sensitivity - { - get => _sensitivity; - set - { - _sensitivity = value; - OnPropertyChanged(); - } - } - - private double _gryoDeadzone; - public double GyroDeadzone - { - get => _gryoDeadzone; - set - { - _gryoDeadzone = value; - OnPropertyChanged(); - } - } - - private bool _enableCemuHookMotion; - public bool EnableCemuHookMotion - { - get => _enableCemuHookMotion; - set - { - _enableCemuHookMotion = value; - OnPropertyChanged(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs deleted file mode 100644 index 49de1993..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Ryujinx.Ava.UI.ViewModels -{ - public class RumbleInputViewModel : BaseModel - { - private float _strongRumble; - public float StrongRumble - { - get => _strongRumble; - set - { - _strongRumble = value; - OnPropertyChanged(); - } - } - - private float _weakRumble; - public float WeakRumble - { - get => _weakRumble; - set - { - _weakRumble = value; - OnPropertyChanged(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs deleted file mode 100644 index bcaa0860..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs +++ /dev/null @@ -1,614 +0,0 @@ -using Avalonia.Collections; -using Avalonia.Controls; -using Avalonia.Threading; -using LibHac.Tools.FsSystem; -using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; -using Ryujinx.Audio.Backends.SoundIo; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Multiplayer; -using Ryujinx.Common.GraphicsDriver; -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 System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net.NetworkInformation; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class SettingsViewModel : BaseModel - { - private readonly VirtualFileSystem _virtualFileSystem; - private readonly ContentManager _contentManager; - private TimeZoneContentManager _timeZoneContentManager; - - private readonly List<string> _validTzRegions; - - private readonly Dictionary<string, string> _networkInterfaces; - - private float _customResolutionScale; - private int _resolutionScale; - private int _graphicsBackendMultithreadingIndex; - private float _volume; - private bool _isVulkanAvailable = true; - private bool _directoryChanged; - private readonly List<string> _gpuIds = new(); - private KeyboardHotkeys _keyboardHotkeys; - private int _graphicsBackendIndex; - private int _scalingFilter; - private int _scalingFilterLevel; - - public event Action CloseWindow; - public event Action SaveSettingsEvent; - private int _networkInterfaceIndex; - private int _multiplayerModeIndex; - - public int ResolutionScale - { - get => _resolutionScale; - set - { - _resolutionScale = value; - - OnPropertyChanged(nameof(CustomResolutionScale)); - OnPropertyChanged(nameof(IsCustomResolutionScaleActive)); - } - } - - public int GraphicsBackendMultithreadingIndex - { - get => _graphicsBackendMultithreadingIndex; - set - { - _graphicsBackendMultithreadingIndex = value; - - if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], - "", - "", - LocaleManager.Instance[LocaleKeys.InputDialogOk], - LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) - ); - } - - OnPropertyChanged(); - } - } - - public float CustomResolutionScale - { - get => _customResolutionScale; - set - { - _customResolutionScale = MathF.Round(value, 1); - - OnPropertyChanged(); - } - } - - public bool IsVulkanAvailable - { - get => _isVulkanAvailable; - set - { - _isVulkanAvailable = value; - - OnPropertyChanged(); - } - } - - public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS(); - - public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; - - public bool DirectoryChanged - { - get => _directoryChanged; - set - { - _directoryChanged = value; - - OnPropertyChanged(); - } - } - - public bool IsMacOS => OperatingSystem.IsMacOS(); - - public bool EnableDiscordIntegration { get; set; } - public bool CheckUpdatesOnStart { get; set; } - public bool ShowConfirmExit { get; set; } - public int HideCursor { get; set; } - public bool EnableDockedMode { get; set; } - public bool EnableKeyboard { get; set; } - public bool EnableMouse { get; set; } - public bool EnableVsync { get; set; } - public bool EnablePptc { get; set; } - public bool EnableInternetAccess { get; set; } - public bool EnableFsIntegrityChecks { get; set; } - public bool IgnoreMissingServices { get; set; } - public bool ExpandDramSize { get; set; } - public bool EnableShaderCache { get; set; } - public bool EnableTextureRecompression { get; set; } - public bool EnableMacroHLE { get; set; } - public bool EnableColorSpacePassthrough { get; set; } - public bool ColorSpacePassthroughAvailable => IsMacOS; - public bool EnableFileLog { get; set; } - public bool EnableStub { get; set; } - public bool EnableInfo { get; set; } - public bool EnableWarn { get; set; } - public bool EnableError { get; set; } - public bool EnableTrace { get; set; } - public bool EnableGuest { get; set; } - public bool EnableFsAccessLog { get; set; } - public bool EnableDebug { get; set; } - public bool IsOpenAlEnabled { get; set; } - public bool IsSoundIoEnabled { get; set; } - public bool IsSDL2Enabled { get; set; } - public bool IsCustomResolutionScaleActive => _resolutionScale == 4; - public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; - - public bool IsVulkanSelected => GraphicsBackendIndex == 0; - public bool UseHypervisor { get; set; } - - public string TimeZone { get; set; } - public string ShaderDumpPath { get; set; } - - public int Language { get; set; } - public int Region { get; set; } - public int FsGlobalAccessLogMode { get; set; } - public int AudioBackend { get; set; } - public int MaxAnisotropy { get; set; } - public int AspectRatio { get; set; } - public int AntiAliasingEffect { get; set; } - public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0"); - public int ScalingFilterLevel - { - get => _scalingFilterLevel; - set - { - _scalingFilterLevel = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(ScalingFilterLevelText)); - } - } - public int OpenglDebugLevel { get; set; } - public int MemoryMode { get; set; } - public int BaseStyleIndex { get; set; } - public int GraphicsBackendIndex - { - get => _graphicsBackendIndex; - set - { - _graphicsBackendIndex = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(IsVulkanSelected)); - } - } - public int ScalingFilter - { - get => _scalingFilter; - set - { - _scalingFilter = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(IsScalingFilterActive)); - } - } - - public int PreferredGpuIndex { get; set; } - - public float Volume - { - get => _volume; - set - { - _volume = value; - - ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100; - - OnPropertyChanged(); - } - } - - public DateTimeOffset CurrentDate { get; set; } - public TimeSpan CurrentTime { get; set; } - - internal AvaloniaList<TimeZone> TimeZones { get; set; } - public AvaloniaList<string> GameDirectories { get; set; } - public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; } - - public AvaloniaList<string> NetworkInterfaceList - { - get => new(_networkInterfaces.Keys); - } - - public AvaloniaList<string> MultiplayerModes - { - get => new(Enum.GetNames<MultiplayerMode>()); - } - - public KeyboardHotkeys KeyboardHotkeys - { - get => _keyboardHotkeys; - set - { - _keyboardHotkeys = value; - - OnPropertyChanged(); - } - } - - public int NetworkInterfaceIndex - { - get => _networkInterfaceIndex; - set - { - _networkInterfaceIndex = value != -1 ? value : 0; - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]]; - } - } - - public int MultiplayerModeIndex - { - get => _multiplayerModeIndex; - set - { - _multiplayerModeIndex = value; - ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex; - } - } - - public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() - { - _virtualFileSystem = virtualFileSystem; - _contentManager = contentManager; - if (Program.PreviewerDetached) - { - Task.Run(LoadTimeZones); - } - } - - public SettingsViewModel() - { - GameDirectories = new AvaloniaList<string>(); - TimeZones = new AvaloniaList<TimeZone>(); - AvailableGpus = new ObservableCollection<ComboBoxItem>(); - _validTzRegions = new List<string>(); - _networkInterfaces = new Dictionary<string, string>(); - - Task.Run(CheckSoundBackends); - Task.Run(PopulateNetworkInterfaces); - - if (Program.PreviewerDetached) - { - Task.Run(LoadAvailableGpus); - LoadCurrentConfiguration(); - } - } - - public async Task CheckSoundBackends() - { - IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; - IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; - IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; - - await Dispatcher.UIThread.InvokeAsync(() => - { - OnPropertyChanged(nameof(IsOpenAlEnabled)); - OnPropertyChanged(nameof(IsSoundIoEnabled)); - OnPropertyChanged(nameof(IsSDL2Enabled)); - }); - } - - private async Task LoadAvailableGpus() - { - AvailableGpus.Clear(); - - var devices = VulkanRenderer.GetPhysicalDevices(); - - if (devices.Length == 0) - { - IsVulkanAvailable = false; - GraphicsBackendIndex = 1; - } - else - { - foreach (var device in devices) - { - await Dispatcher.UIThread.InvokeAsync(() => - { - _gpuIds.Add(device.Id); - - AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" }); - }); - } - } - - // GPU configuration needs to be loaded during the async method or it will always return 0. - PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ? - _gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0; - - Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex))); - } - - public async Task LoadTimeZones() - { - _timeZoneContentManager = new TimeZoneContentManager(); - - _timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None); - - foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets()) - { - int hours = Math.DivRem(offset, 3600, out int seconds); - int minutes = Math.Abs(seconds) / 60; - - string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr; - - await Dispatcher.UIThread.InvokeAsync(() => - { - TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2)); - - _validTzRegions.Add(location); - }); - } - - Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone))); - } - - private async Task PopulateNetworkInterfaces() - { - _networkInterfaces.Clear(); - _networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0"); - - foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) - { - await Dispatcher.UIThread.InvokeAsync(() => - { - _networkInterfaces.Add(networkInterface.Name, networkInterface.Id); - }); - } - - // Network interface index needs to be loaded during the async method or it will always return 0. - NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); - - Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex))); - } - - public void ValidateAndSetTimeZone(string location) - { - if (_validTzRegions.Contains(location)) - { - TimeZone = location; - } - } - - public void LoadCurrentConfiguration() - { - ConfigurationState config = ConfigurationState.Instance; - - // User Interface - EnableDiscordIntegration = config.EnableDiscordIntegration; - CheckUpdatesOnStart = config.CheckUpdatesOnStart; - ShowConfirmExit = config.ShowConfirmExit; - HideCursor = (int)config.HideCursor.Value; - - GameDirectories.Clear(); - GameDirectories.AddRange(config.UI.GameDirs.Value); - - BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1; - - // Input - EnableDockedMode = config.System.EnableDockedMode; - EnableKeyboard = config.Hid.EnableKeyboard; - EnableMouse = config.Hid.EnableMouse; - - // Keyboard Hotkeys - KeyboardHotkeys = config.Hid.Hotkeys.Value; - - // System - Region = (int)config.System.Region.Value; - Language = (int)config.System.Language.Value; - TimeZone = config.System.TimeZone; - - DateTime currentDateTime = DateTime.Now; - - CurrentDate = currentDateTime.Date; - CurrentTime = currentDateTime.TimeOfDay.Add(TimeSpan.FromSeconds(config.System.SystemTimeOffset)); - - EnableVsync = config.Graphics.EnableVsync; - EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; - ExpandDramSize = config.System.ExpandRam; - IgnoreMissingServices = config.System.IgnoreMissingServices; - - // CPU - EnablePptc = config.System.EnablePtc; - MemoryMode = (int)config.System.MemoryManagerMode.Value; - UseHypervisor = config.System.UseHypervisor; - - // Graphics - GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value; - // Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus(). - EnableShaderCache = config.Graphics.EnableShaderCache; - EnableTextureRecompression = config.Graphics.EnableTextureRecompression; - EnableMacroHLE = config.Graphics.EnableMacroHLE; - EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough; - ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1; - CustomResolutionScale = config.Graphics.ResScaleCustom; - MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy)); - AspectRatio = (int)config.Graphics.AspectRatio.Value; - GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value; - ShaderDumpPath = config.Graphics.ShadersDumpPath; - AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value; - ScalingFilter = (int)config.Graphics.ScalingFilter.Value; - ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value; - - // Audio - AudioBackend = (int)config.System.AudioBackend.Value; - Volume = config.System.AudioVolume * 100; - - // Network - EnableInternetAccess = config.System.EnableInternetAccess; - // LAN interface index is loaded asynchronously in PopulateNetworkInterfaces() - - // Logging - EnableFileLog = config.Logger.EnableFileLog; - EnableStub = config.Logger.EnableStub; - EnableInfo = config.Logger.EnableInfo; - EnableWarn = config.Logger.EnableWarn; - EnableError = config.Logger.EnableError; - EnableTrace = config.Logger.EnableTrace; - EnableGuest = config.Logger.EnableGuest; - EnableDebug = config.Logger.EnableDebug; - EnableFsAccessLog = config.Logger.EnableFsAccessLog; - FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; - OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; - - MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; - } - - public void SaveSettings() - { - ConfigurationState config = ConfigurationState.Instance; - - // User Interface - config.EnableDiscordIntegration.Value = EnableDiscordIntegration; - config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; - config.ShowConfirmExit.Value = ShowConfirmExit; - config.HideCursor.Value = (HideCursorMode)HideCursor; - - if (_directoryChanged) - { - List<string> gameDirs = new(GameDirectories); - config.UI.GameDirs.Value = gameDirs; - } - - config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; - - // Input - config.System.EnableDockedMode.Value = EnableDockedMode; - config.Hid.EnableKeyboard.Value = EnableKeyboard; - config.Hid.EnableMouse.Value = EnableMouse; - - // Keyboard Hotkeys - config.Hid.Hotkeys.Value = KeyboardHotkeys; - - // System - config.System.Region.Value = (Region)Region; - config.System.Language.Value = (Language)Language; - - if (_validTzRegions.Contains(TimeZone)) - { - config.System.TimeZone.Value = TimeZone; - } - - config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds()); - config.Graphics.EnableVsync.Value = EnableVsync; - config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; - config.System.ExpandRam.Value = ExpandDramSize; - config.System.IgnoreMissingServices.Value = IgnoreMissingServices; - - // CPU - config.System.EnablePtc.Value = EnablePptc; - config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; - config.System.UseHypervisor.Value = UseHypervisor; - - // Graphics - config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex; - config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); - config.Graphics.EnableShaderCache.Value = EnableShaderCache; - config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression; - config.Graphics.EnableMacroHLE.Value = EnableMacroHLE; - config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough; - config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1; - config.Graphics.ResScaleCustom.Value = CustomResolutionScale; - config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy); - config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio; - config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect; - config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter; - config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel; - - if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex) - { - DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off); - } - - config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex; - config.Graphics.ShadersDumpPath.Value = ShaderDumpPath; - - // Audio - AudioBackend audioBackend = (AudioBackend)AudioBackend; - if (audioBackend != config.System.AudioBackend.Value) - { - config.System.AudioBackend.Value = audioBackend; - - Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}"); - } - - config.System.AudioVolume.Value = Volume / 100; - - // Network - config.System.EnableInternetAccess.Value = EnableInternetAccess; - - // Logging - config.Logger.EnableFileLog.Value = EnableFileLog; - config.Logger.EnableStub.Value = EnableStub; - config.Logger.EnableInfo.Value = EnableInfo; - config.Logger.EnableWarn.Value = EnableWarn; - config.Logger.EnableError.Value = EnableError; - config.Logger.EnableTrace.Value = EnableTrace; - config.Logger.EnableGuest.Value = EnableGuest; - config.Logger.EnableDebug.Value = EnableDebug; - config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; - config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; - config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; - - config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; - config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; - - config.ToFileFormat().SaveConfig(Program.ConfigurationPath); - - MainWindow.UpdateGraphicsConfig(); - - SaveSettingsEvent?.Invoke(); - - _directoryChanged = false; - } - - private static void RevertIfNotSaved() - { - Program.ReloadConfig(); - } - - public void ApplyButton() - { - SaveSettings(); - } - - public void OkButton() - { - SaveSettings(); - CloseWindow?.Invoke(); - } - - public void CancelButton() - { - RevertIfNotSaved(); - CloseWindow?.Invoke(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs deleted file mode 100644 index 5989ce09..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ /dev/null @@ -1,249 +0,0 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Platform.Storage; -using Avalonia.Threading; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Ns; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.FileSystem; -using Ryujinx.UI.App.Common; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Path = System.IO.Path; -using SpanHelpers = LibHac.Common.SpanHelpers; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class TitleUpdateViewModel : BaseModel - { - public TitleUpdateMetadata TitleUpdateWindowData; - public readonly string TitleUpdateJsonPath; - private VirtualFileSystem VirtualFileSystem { get; } - private ulong TitleId { get; } - - private AvaloniaList<TitleUpdateModel> _titleUpdates = new(); - private AvaloniaList<object> _views = new(); - private object _selectedUpdate; - - private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AvaloniaList<TitleUpdateModel> TitleUpdates - { - get => _titleUpdates; - set - { - _titleUpdates = value; - OnPropertyChanged(); - } - } - - public AvaloniaList<object> Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public object SelectedUpdate - { - get => _selectedUpdate; - set - { - _selectedUpdate = value; - OnPropertyChanged(); - } - } - - public IStorageProvider StorageProvider; - - public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId) - { - VirtualFileSystem = virtualFileSystem; - - TitleId = titleId; - - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - StorageProvider = desktop.MainWindow.StorageProvider; - } - - TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); - - try - { - TitleUpdateWindowData = JsonHelper.DeserializeFromFile(TitleUpdateJsonPath, _serializerContext.TitleUpdateMetadata); - } - catch - { - Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}"); - - TitleUpdateWindowData = new TitleUpdateMetadata - { - Selected = "", - Paths = new List<string>(), - }; - - Save(); - } - - LoadUpdates(); - } - - private void LoadUpdates() - { - foreach (string path in TitleUpdateWindowData.Paths) - { - AddUpdate(path); - } - - TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == TitleUpdateWindowData.Selected, null); - - SelectedUpdate = selected; - - // NOTE: Save the list again to remove leftovers. - Save(); - SortUpdates(); - } - - public void SortUpdates() - { - var list = TitleUpdates.ToList(); - - list.Sort((first, second) => - { - if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) - { - return -1; - } - - if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) - { - return 1; - } - - return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; - }); - - Views.Clear(); - Views.Add(new BaseModel()); - Views.AddRange(list); - - if (SelectedUpdate == null) - { - SelectedUpdate = Views[0]; - } - else if (!TitleUpdates.Contains(SelectedUpdate)) - { - if (Views.Count > 1) - { - SelectedUpdate = Views[1]; - } - else - { - SelectedUpdate = Views[0]; - } - } - } - - private void AddUpdate(string path) - { - if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) - { - using FileStream file = new(path, FileMode.Open, FileAccess.Read); - - try - { - var pfs = new PartitionFileSystem(); - pfs.Initialize(file.AsStorage()).ThrowIfFailure(); - (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0); - - if (controlNca != null && patchNca != null) - { - ApplicationControlProperty controlData = new(); - - using UniqueRef<IFile> nacpFile = new(); - - controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); - - TitleUpdates.Add(new TitleUpdateModel(controlData, path)); - } - else - { - Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage])); - } - } - catch (Exception ex) - { - Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path))); - } - } - } - - public void RemoveUpdate(TitleUpdateModel update) - { - TitleUpdates.Remove(update); - - SortUpdates(); - } - - public async Task Add() - { - var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - AllowMultiple = true, - FileTypeFilter = new List<FilePickerFileType> - { - new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) - { - Patterns = new[] { "*.nsp" }, - AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" }, - }, - }, - }); - - foreach (var file in result) - { - AddUpdate(file.Path.LocalPath); - } - - SortUpdates(); - } - - public void Save() - { - TitleUpdateWindowData.Paths.Clear(); - TitleUpdateWindowData.Selected = ""; - - foreach (TitleUpdateModel update in TitleUpdates) - { - TitleUpdateWindowData.Paths.Add(update.Path); - - if (update == SelectedUpdate) - { - TitleUpdateWindowData.Selected = update.Path; - } - } - - JsonHelper.SerializeToFile(TitleUpdateJsonPath, TitleUpdateWindowData, _serializerContext.TitleUpdateMetadata); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs deleted file mode 100644 index 89b59122..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Avalonia.Media; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Ncm; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.UI.Models; -using Ryujinx.HLE.FileSystem; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using Color = Avalonia.Media.Color; - -namespace Ryujinx.Ava.UI.ViewModels -{ - internal class UserFirmwareAvatarSelectorViewModel : BaseModel - { - private static readonly Dictionary<string, byte[]> _avatarStore = new(); - - private ObservableCollection<ProfileImageModel> _images; - private Color _backgroundColor = Colors.White; - - private int _selectedIndex; - - public UserFirmwareAvatarSelectorViewModel() - { - _images = new ObservableCollection<ProfileImageModel>(); - - LoadImagesFromStore(); - } - - public Color BackgroundColor - { - get => _backgroundColor; - set - { - _backgroundColor = value; - OnPropertyChanged(); - ChangeImageBackground(); - } - } - - public ObservableCollection<ProfileImageModel> Images - { - get => _images; - set - { - _images = value; - OnPropertyChanged(); - } - } - - public int SelectedIndex - { - get => _selectedIndex; - set - { - _selectedIndex = value; - - if (_selectedIndex == -1) - { - SelectedImage = null; - } - else - { - SelectedImage = _images[_selectedIndex].Data; - } - - OnPropertyChanged(); - } - } - - public byte[] SelectedImage { get; private set; } - - private void LoadImagesFromStore() - { - Images.Clear(); - - foreach (var image in _avatarStore) - { - Images.Add(new ProfileImageModel(image.Key, image.Value)); - } - } - - private void ChangeImageBackground() - { - foreach (var image in Images) - { - image.BackgroundColor = new SolidColorBrush(BackgroundColor); - } - } - - public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) - { - if (_avatarStore.Count > 0) - { - return; - } - - string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); - string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - - if (!string.IsNullOrWhiteSpace(avatarPath)) - { - using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); - - Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); - IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - foreach (DirectoryEntryEx item in romfs.EnumerateEntries()) - { - // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. - if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) - { - using var file = new UniqueRef<IFile>(); - - romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - using MemoryStream stream = new(); - using MemoryStream streamPng = new(); - - file.Get.AsStream().CopyTo(stream); - - stream.Position = 0; - - Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); - - avatarImage.SaveAsPng(streamPng); - - _avatarStore.Add(item.FullPath, streamPng.ToArray()); - } - } - } - } - - private static byte[] DecompressYaz0(Stream stream) - { - using BinaryReader reader = new(stream); - - reader.ReadInt32(); // Magic - - uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); - - reader.ReadInt64(); // Padding - - byte[] input = new byte[stream.Length - stream.Position]; - stream.Read(input, 0, input.Length); - - uint inputOffset = 0; - - byte[] output = new byte[decodedLength]; - uint outputOffset = 0; - - ushort mask = 0; - byte header = 0; - - while (outputOffset < decodedLength) - { - if ((mask >>= 1) == 0) - { - header = input[inputOffset++]; - mask = 0x80; - } - - if ((header & mask) != 0) - { - if (outputOffset == output.Length) - { - break; - } - - output[outputOffset++] = input[inputOffset++]; - } - else - { - byte byte1 = input[inputOffset++]; - byte byte2 = input[inputOffset++]; - - uint dist = (uint)((byte1 & 0xF) << 8) | byte2; - uint position = outputOffset - (dist + 1); - - uint length = (uint)byte1 >> 4; - if (length == 0) - { - length = (uint)input[inputOffset++] + 0x12; - } - else - { - length += 2; - } - - uint gap = outputOffset - position; - uint nonOverlappingLength = length; - - if (nonOverlappingLength > gap) - { - nonOverlappingLength = gap; - } - - Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength); - outputOffset += nonOverlappingLength; - position += nonOverlappingLength; - length -= nonOverlappingLength; - - while (length-- > 0) - { - output[outputOffset++] = output[position++]; - } - } - } - - return output; - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs deleted file mode 100644 index 8e7d41a5..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ryujinx.Ava.UI.ViewModels -{ - internal class UserProfileImageSelectorViewModel : BaseModel - { - private bool _firmwareFound; - - public bool FirmwareFound - { - get => _firmwareFound; - - set - { - _firmwareFound = value; - OnPropertyChanged(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs deleted file mode 100644 index 70274847..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.IdentityModel.Tokens; -using Ryujinx.Ava.UI.Models; -using System; -using System.Collections.ObjectModel; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class UserProfileViewModel : BaseModel, IDisposable - { - public UserProfileViewModel() - { - Profiles = new ObservableCollection<BaseModel>(); - LostProfiles = new ObservableCollection<UserProfile>(); - IsEmpty = LostProfiles.IsNullOrEmpty(); - } - - public ObservableCollection<BaseModel> Profiles { get; set; } - - public ObservableCollection<UserProfile> LostProfiles { get; set; } - - public bool IsEmpty { get; set; } - - public void Dispose() - { - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs deleted file mode 100644 index 85adef00..00000000 --- a/src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs +++ /dev/null @@ -1,117 +0,0 @@ -using DynamicData; -using DynamicData.Binding; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace Ryujinx.Ava.UI.ViewModels -{ - public class UserSaveManagerViewModel : BaseModel - { - private int _sortIndex; - private int _orderIndex; - private string _search; - private ObservableCollection<SaveModel> _saves = new(); - private ObservableCollection<SaveModel> _views = new(); - private readonly AccountManager _accountManager; - - public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); - - public int SortIndex - { - get => _sortIndex; - set - { - _sortIndex = value; - OnPropertyChanged(); - Sort(); - } - } - - public int OrderIndex - { - get => _orderIndex; - set - { - _orderIndex = value; - OnPropertyChanged(); - Sort(); - } - } - - public string Search - { - get => _search; - set - { - _search = value; - OnPropertyChanged(); - Sort(); - } - } - - public ObservableCollection<SaveModel> Saves - { - get => _saves; - set - { - _saves = value; - OnPropertyChanged(); - Sort(); - } - } - - public ObservableCollection<SaveModel> Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public UserSaveManagerViewModel(AccountManager accountManager) - { - _accountManager = accountManager; - } - - public void Sort() - { - Saves.AsObservableChangeSet() - .Filter(Filter) - .Sort(GetComparer()) - .Bind(out var view).AsObservableList(); - - _views.Clear(); - _views.AddRange(view); - OnPropertyChanged(nameof(Views)); - } - - private bool Filter(object arg) - { - if (arg is SaveModel save) - { - return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); - } - - return false; - } - - private IComparer<SaveModel> GetComparer() - { - return SortIndex switch - { - 0 => OrderIndex == 0 - ? SortExpressionComparer<SaveModel>.Ascending(save => save.Title) - : SortExpressionComparer<SaveModel>.Descending(save => save.Title), - 1 => OrderIndex == 0 - ? SortExpressionComparer<SaveModel>.Ascending(save => save.Size) - : SortExpressionComparer<SaveModel>.Descending(save => save.Size), - _ => null, - }; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml deleted file mode 100644 index 99f2b6b6..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml +++ /dev/null @@ -1,1130 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - d:DesignHeight="800" - d:DesignWidth="800" - x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView" - x:DataType="viewModels:ControllerInputViewModel" - mc:Ignorable="d" - Focusable="True"> - <Design.DataContext> - <viewModels:ControllerInputViewModel /> - </Design.DataContext> - <UserControl.Resources> - <helpers:KeyValueConverter x:Key="Key" /> - </UserControl.Resources> - <UserControl.Styles> - <Style Selector="ToggleButton"> - <Setter Property="Width" Value="90" /> - <Setter Property="Height" Value="27" /> - <Setter Property="HorizontalAlignment" Value="Stretch" /> - </Style> - </UserControl.Styles> - <StackPanel - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Orientation="Vertical"> - <StackPanel - Margin="0 0 0 5" - Orientation="Vertical" - Spacing="5"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="10" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <!-- Player Selection --> - <Grid - Grid.Column="0" - Margin="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Center"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <TextBlock - Margin="5,0,10,0" - Width="90" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsPlayer}" /> - <ComboBox - Grid.Column="1" - Name="PlayerIndexBox" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - SelectionChanged="PlayerIndexBox_OnSelectionChanged" - ItemsSource="{Binding PlayerIndexes}" - SelectedIndex="{Binding PlayerId}"> - <ComboBox.ItemTemplate> - <DataTemplate> - <TextBlock Text="{Binding Name}" /> - </DataTemplate> - </ComboBox.ItemTemplate> - </ComboBox> - </Grid> - <!-- Profile Selection --> - <Grid - Grid.Column="2" - Margin="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Center"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> - <TextBlock - Margin="5,0,10,0" - Width="90" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsProfile}" /> - <ui:FAComboBox - Grid.Column="1" - IsEditable="True" - Name="ProfileBox" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - SelectedIndex="0" - ItemsSource="{Binding ProfilesList}" - Text="{Binding ProfileName, Mode=TwoWay}" /> - <Button - Grid.Column="2" - MinWidth="0" - Margin="5,0,0,0" - VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}" - Command="{ReflectionBinding LoadProfile}"> - <ui:SymbolIcon - Symbol="Upload" - FontSize="15" - Height="20" /> - </Button> - <Button - Grid.Column="3" - MinWidth="0" - Margin="5,0,0,0" - VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}" - Command="{ReflectionBinding SaveProfile}"> - <ui:SymbolIcon - Symbol="Save" - FontSize="15" - Height="20" /> - </Button> - <Button - Grid.Column="4" - MinWidth="0" - Margin="5,0,0,0" - VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}" - Command="{ReflectionBinding RemoveProfile}"> - <ui:SymbolIcon - Symbol="Delete" - FontSize="15" - Height="20" /> - </Button> - </Grid> - </Grid> - <Separator /> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="10" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <!-- Input Device --> - <Grid - Grid.Column="0" - Margin="2" - HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="*"/> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <TextBlock - Grid.Column="0" - Margin="5,0,10,0" - Width="90" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsInputDevice}" /> - <ComboBox - Grid.Column="1" - Name="DeviceBox" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - ItemsSource="{Binding DeviceList}" - SelectedIndex="{Binding Device}" /> - <Button - Grid.Column="2" - MinWidth="0" - Margin="5,0,0,0" - VerticalAlignment="Center" - Command="{ReflectionBinding LoadDevices}"> - <ui:SymbolIcon - Symbol="Refresh" - FontSize="15" - Height="20"/> - </Button> - </Grid> - <!-- Controller Type --> - <Grid - Grid.Column="2" - Margin="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Center"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <TextBlock - Margin="5,0,10,0" - Width="90" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsControllerType}" /> - <ComboBox - Grid.Column="1" - HorizontalAlignment="Stretch" - ItemsSource="{Binding Controllers}" - SelectedIndex="{Binding Controller}"> - <ComboBox.ItemTemplate> - <DataTemplate DataType="models:ControllerModel"> - <TextBlock Text="{Binding Name}" /> - </DataTemplate> - </ComboBox.ItemTemplate> - </ComboBox> - </Grid> - </Grid> - </StackPanel> - <!-- Button / JoyStick Settings --> - <Grid - Name="SettingButtons" - MinHeight="450" - FlowDirection="LeftToRight" - IsVisible="{Binding ShowSettings}"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <!-- Left Controls --> - <StackPanel - Orientation="Vertical" - Margin="0,0,5,0" - Grid.Column="0"> - <!-- Left Triggers --> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - IsVisible="{Binding IsLeft}" - MinHeight="90" - CornerRadius="5"> - <Grid - Margin="10" - HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition /> - <RowDefinition /> - </Grid.RowDefinitions> - <StackPanel - Grid.Column="0" - Grid.Row="0" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsTriggerZL}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Grid.Column="0" - Grid.Row="1" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsTriggerL}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Grid.Column="1" - Grid.Row="1" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonMinus}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </Grid> - </Border> - <!-- Left Joystick --> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - IsVisible="{Binding IsLeft}" - Margin="0,5,0,0" - CornerRadius="5"> - <StackPanel - Margin="10" - Orientation="Vertical"> - <TextBlock - Margin="0,0,0,10" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsLStick}" /> - <!-- Left Joystick Keyboard --> - <StackPanel - IsVisible="{Binding !IsController}" - Orientation="Vertical"> - <!-- Left Joystick Button --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickButton}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left Joystick Up --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickUp}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftStickUp, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left Joystick Down --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickDown}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftStickDown, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left Joystick Left --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickLeft}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftStickLeft, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left Joystick Right --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickRight}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftStickRight, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - <!-- Left Joystick Controller --> - <StackPanel - IsVisible="{Binding IsController}" - Orientation="Vertical"> - <!-- Left Joystick Button --> - <StackPanel - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickButton}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left Joystick Stick --> - <StackPanel - Margin="0,4,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickStick}" - TextAlignment="Center" /> - <ToggleButton Tag="stick"> - <TextBlock - Text="{ReflectionBinding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <Separator - Margin="0,8,0,8" - Height="1" /> - <CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickX}"> - <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> - </CheckBox> - <CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickY}"> - <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> - </CheckBox> - <CheckBox IsChecked="{ReflectionBinding Configuration.LeftRotate90}"> - <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> - </CheckBox> - <Separator - Margin="0,8,0,8" - Height="1" /> - <StackPanel Orientation="Vertical"> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickDeadzone}" /> - <StackPanel - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <controls:SliderScroll - Width="130" - Maximum="1" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Minimum="0" - Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Width="25" - Text="{ReflectionBinding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" /> - </StackPanel> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickRange}" /> - <StackPanel - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <controls:SliderScroll - Width="130" - Maximum="2" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Minimum="0" - Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Width="25" - Text="{ReflectionBinding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" /> - </StackPanel> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - <!-- Left DPad --> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - VerticalAlignment="Top" - IsVisible="{Binding IsLeft}" - Margin="0,5,0,0" - CornerRadius="5"> - <StackPanel - Margin="10" - Orientation="Vertical"> - <TextBlock - Margin="0,0,0,10" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsDPad}" /> - <StackPanel Orientation="Vertical"> - <!-- Left DPad Up --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsDPadUp}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left DPad Down --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsDPadDown}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left DPad Left --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsDPadLeft}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Left DPad Right --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsDPadRight}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - </StackPanel> - <!-- Triggers & Side Buttons --> - <StackPanel - Grid.Column="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - MinHeight="90"> - <StackPanel - Margin="8" - Orientation="Vertical"> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsTriggerThreshold}" /> - <StackPanel - HorizontalAlignment="Center" - Orientation="Horizontal"> - <controls:SliderScroll - Width="130" - Maximum="1" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Minimum="0" - Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" /> - <TextBlock - Width="25" - Text="{ReflectionBinding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" /> - </StackPanel> - <StackPanel - Margin="0,4,0,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - IsVisible="{Binding !IsRight}" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsLeftSR}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Margin="0,4,0,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - IsVisible="{Binding !IsRight}" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsLeftSL}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.LeftButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Margin="0,4,0,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - IsVisible="{Binding !IsLeft}" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsRightSR}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Margin="0,4,0,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - IsVisible="{Binding !IsLeft}" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsRightSL}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - </Border> - <!-- Controller Picture --> - <Image - Margin="0,10,0,0" - MaxHeight="300" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Source="{Binding Image}" /> - <!-- Motion + Rumble --> - <StackPanel - Margin="0,10,0,0" - Spacing="5" - Orientation="Vertical" - VerticalAlignment="Bottom"> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - VerticalAlignment="Bottom" - HorizontalAlignment="Stretch" - IsVisible="{Binding IsController}"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <CheckBox - Margin="10" - MinWidth="0" - Grid.Column="0" - IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}"> - <TextBlock Text="{locale:Locale ControllerSettingsMotion}" /> - </CheckBox> - <Button - Margin="10" - Grid.Column="1" - Command="{Binding ShowMotionConfig}"> - <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> - </Button> - </Grid> - </Border> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - HorizontalAlignment="Stretch" - IsVisible="{Binding IsController}" - Margin="0,-1,0,0"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <CheckBox - Margin="10" - MinWidth="0" - Grid.Column="0" - IsChecked="{ReflectionBinding Configuration.EnableRumble, Mode=TwoWay}"> - <TextBlock Text="{locale:Locale ControllerSettingsRumble}" /> - </CheckBox> - <Button - Margin="10" - Grid.Column="1" - Command="{Binding ShowRumbleConfig}"> - <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> - </Button> - </Grid> - </Border> - </StackPanel> - </StackPanel> - <!-- Right Controls --> - <StackPanel - Orientation="Vertical" - Margin="5,0,0,0" - Grid.Column="2"> - <!-- Right Triggers --> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - IsVisible="{Binding IsRight}" - MinHeight="90" - CornerRadius="5"> - <Grid - Margin="10" - HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition /> - <RowDefinition /> - </Grid.RowDefinitions> - <StackPanel - Grid.Column="1" - Grid.Row="0" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsTriggerZR}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Grid.Column="1" - Grid.Row="1" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsTriggerR}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel - Grid.Column="0" - Grid.Row="1" - HorizontalAlignment="Right" - VerticalAlignment="Center" - Orientation="Horizontal"> - <TextBlock - Width="20" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonPlus}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </Grid> - </Border> - <!-- Right Joystick --> - <Border - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - IsVisible="{Binding IsRight}" - Margin="0,5,0,0" - CornerRadius="5"> - <StackPanel - Margin="10" - Orientation="Vertical"> - <TextBlock - Margin="0,0,0,10" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtons}" /> - <StackPanel - Orientation="Vertical"> - <!-- Right Buttons A --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Width="120" - Margin="0,0,10,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonA}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Buttons B --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Width="120" - Margin="0,0,10,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonB}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Buttons X --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Width="120" - Margin="0,0,10,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonX}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Buttons Y --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Width="120" - Margin="0,0,10,0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsButtonY}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - <!-- Right DPad --> - <Border - Padding="10" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - IsVisible="{Binding IsRight}" - Margin="0,5,0,0"> - <StackPanel Orientation="Vertical"> - <TextBlock - Margin="0,0,0,10" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsRStick}" /> - <!-- Right Joystick Keyboard --> - <StackPanel - IsVisible="{Binding !IsController}" - Orientation="Vertical"> - <!-- Right Joystick Button --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickButton}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Joystick Up --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickUp}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightStickUp, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Joystick Down --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickDown}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightStickDown, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Joystick Left --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickLeft}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightStickLeft, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Joystick Right --> - <StackPanel - Margin="0,0,0,4" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickRight}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightStickRight, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - <!-- Right Joystick Controller --> - <StackPanel - IsVisible="{Binding IsController}" - Orientation="Vertical"> - <!-- Right Joystick Button --> - <StackPanel - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickButton}" - TextAlignment="Center" /> - <ToggleButton> - <TextBlock - Text="{ReflectionBinding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <!-- Right Joystick Stick --> - <StackPanel - Margin="0,4,0,4" - Background="{DynamicResource ThemeDarkColor}" - Orientation="Horizontal"> - <TextBlock - Margin="0,0,10,0" - Width="120" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickStick}" - TextAlignment="Center" /> - <ToggleButton Tag="stick"> - <TextBlock - Text="{ReflectionBinding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <Separator Margin="0,8,0,8" Height="1" /> - <CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickX}"> - <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> - </CheckBox> - <CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickY}"> - <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> - </CheckBox> - <CheckBox IsChecked="{ReflectionBinding Configuration.RightRotate90}"> - <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> - </CheckBox> - <Separator Margin="0,8,0,8" Height="1" /> - <StackPanel Orientation="Vertical"> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickDeadzone}" /> - <StackPanel - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <controls:SliderScroll - Width="130" - Maximum="1" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Padding="0" - VerticalAlignment="Center" - Minimum="0" - Value="{ReflectionBinding Configuration.DeadzoneRight, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Width="25" - Text="{ReflectionBinding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" /> - </StackPanel> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsStickRange}" /> - <StackPanel - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <controls:SliderScroll - Width="130" - Maximum="2" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Minimum="0" - Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Width="25" - Text="{ReflectionBinding Configuration.RangeRight, StringFormat=\{0:0.00\}}" /> - </StackPanel> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - </StackPanel> - </Grid> - </StackPanel> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs deleted file mode 100644 index 35129706..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.LogicalTree; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Input; -using Ryujinx.Input.Assigner; -using System; - -namespace Ryujinx.Ava.UI.Views.Input -{ - public partial class ControllerInputView : UserControl - { - private bool _dialogOpen; - - private ButtonKeyAssigner _currentAssigner; - internal ControllerInputViewModel ViewModel { get; set; } - - public ControllerInputView() - { - DataContext = ViewModel = new ControllerInputViewModel(this); - - InitializeComponent(); - - foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) - { - if (visual is ToggleButton button && visual is not CheckBox) - { - button.IsCheckedChanged += Button_IsCheckedChanged; - } - } - } - - protected override void OnPointerReleased(PointerReleasedEventArgs e) - { - base.OnPointerReleased(e); - - if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) - { - _currentAssigner.Cancel(); - } - } - - private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) - { - if (sender is ToggleButton button) - { - if ((bool)button.IsChecked) - { - if (_currentAssigner != null && button == _currentAssigner.ToggledButton) - { - return; - } - - bool isStick = button.Tag != null && button.Tag.ToString() == "stick"; - - if (_currentAssigner == null) - { - _currentAssigner = new ButtonKeyAssigner(button); - - this.Focus(NavigationMethod.Pointer); - - PointerPressed += MouseClick; - - IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. - IButtonAssigner assigner = CreateButtonAssigner(isStick); - - _currentAssigner.ButtonAssigned += (sender, e) => - { - if (e.IsAssigned) - { - ViewModel.IsModified = true; - } - }; - - _currentAssigner.GetInputAndAssign(assigner, keyboard); - } - else - { - if (_currentAssigner != null) - { - ToggleButton oldButton = _currentAssigner.ToggledButton; - - _currentAssigner.Cancel(); - _currentAssigner = null; - button.IsChecked = false; - } - } - } - else - { - _currentAssigner?.Cancel(); - _currentAssigner = null; - } - } - } - - public void SaveCurrentProfile() - { - ViewModel.Save(); - } - - private IButtonAssigner CreateButtonAssigner(bool forStick) - { - IButtonAssigner assigner; - - var device = ViewModel.Devices[ViewModel.Device]; - - if (device.Type == DeviceType.Keyboard) - { - assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad); - } - else if (device.Type == DeviceType.Controller) - { - assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick); - } - else - { - throw new Exception("Controller not supported"); - } - - return assigner; - } - - private void MouseClick(object sender, PointerPressedEventArgs e) - { - bool shouldUnbind = false; - - if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed) - { - shouldUnbind = true; - } - - _currentAssigner?.Cancel(shouldUnbind); - - PointerPressed -= MouseClick; - } - - private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (ViewModel.IsModified && !_dialogOpen) - { - _dialogOpen = true; - - var result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage], - LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - ViewModel.Save(); - } - - _dialogOpen = false; - - ViewModel.IsModified = false; - - if (e.AddedItems.Count > 0) - { - var player = (PlayerModel)e.AddedItems[0]; - ViewModel.PlayerId = player.Id; - } - } - } - - public void Dispose() - { - _currentAssigner?.Cancel(); - _currentAssigner = null; - ViewModel.Dispose(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml deleted file mode 100644 index a6b587f6..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml +++ /dev/null @@ -1,171 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" - x:DataType="viewModels:MotionInputViewModel" - Focusable="True"> - <Grid Margin="10"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition /> - </Grid.RowDefinitions> - <StackPanel Orientation="Vertical"> - <StackPanel - Orientation="Horizontal" - HorizontalAlignment="Center"> - <TextBlock - Margin="0" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" /> - <controls:SliderScroll - Margin="0,-5,0,-5" - Width="150" - MaxWidth="150" - TickFrequency="1" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Maximum="100" - Minimum="0" - Value="{Binding Sensitivity, Mode=TwoWay}" /> - <TextBlock - HorizontalAlignment="Center" - Margin="5, 0" - Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" /> - </StackPanel> - <StackPanel - Orientation="Horizontal" - HorizontalAlignment="Center"> - <TextBlock - Margin="0" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" /> - <controls:SliderScroll - Margin="0,-5,0,-5" - Width="150" - MaxWidth="150" - TickFrequency="1" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Maximum="100" - Minimum="0" - Value="{Binding GyroDeadzone, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Margin="5, 0" - Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" /> - </StackPanel> - <Separator - Height="1" - Margin="0,5" /> - <CheckBox - Margin="5" - IsChecked="{Binding EnableCemuHookMotion}"> - <TextBlock - Margin="0,3,0,0" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" /> - </CheckBox> - </StackPanel> - <Border - Grid.Row="1" - Padding="20,5" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1" - CornerRadius="5" - HorizontalAlignment="Stretch"> - <Grid VerticalAlignment="Top"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - </Grid.RowDefinitions> - <StackPanel - Grid.Row="1" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Vertical"> - <StackPanel - HorizontalAlignment="Center" - VerticalAlignment="Center" - Orientation="Horizontal"> - <TextBlock - Margin="5" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionServerHost}" /> - <TextBox - Height="30" - MinWidth="100" - MaxWidth="100" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{Binding DsuServerHost, Mode=TwoWay}" /> - <TextBlock - Margin="5" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text=":" /> - <TextBox - Height="30" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{Binding DsuServerPort, Mode=TwoWay}" /> - </StackPanel> - <StackPanel Orientation="Vertical"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition /> - <RowDefinition /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <TextBlock - Margin="0,10,0,0" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionControllerSlot}" /> - <ui:NumberBox - Grid.Row="0" - Grid.Column="1" - Name="CemuHookSlotUpDown" - SmallChange="1" - LargeChange="1" - Maximum="4" - Minimum="0" - Value="{Binding Slot}" /> - <TextBlock - Margin="0,10,0,0" - Grid.Row="1" - Grid.Column="0" - VerticalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" /> - <ui:NumberBox - Grid.Row="1" - Grid.Column="1" - Name="CemuHookRightJoyConSlotUpDown" - SmallChange="1" - LargeChange="1" - Maximum="4" - Minimum="0" - Value="{Binding AltSlot}" /> - </Grid> - </StackPanel> - <CheckBox - HorizontalAlignment="Center" - IsChecked="{Binding MirrorInput, Mode=TwoWay}"> - <TextBlock - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsMotionMirrorInput}" /> - </CheckBox> - </StackPanel> - </Grid> - </Border> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs deleted file mode 100644 index 1b340752..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Avalonia.Controls; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid.Controller; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Views.Input -{ - public partial class MotionInputView : UserControl - { - private readonly MotionInputViewModel _viewModel; - - public MotionInputView() - { - InitializeComponent(); - } - - public MotionInputView(ControllerInputViewModel viewModel) - { - var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; - - _viewModel = new MotionInputViewModel - { - Slot = config.Slot, - AltSlot = config.AltSlot, - DsuServerHost = config.DsuServerHost, - DsuServerPort = config.DsuServerPort, - MirrorInput = config.MirrorInput, - Sensitivity = config.Sensitivity, - GyroDeadzone = config.GyroDeadzone, - EnableCemuHookMotion = config.EnableCemuHookMotion, - }; - - InitializeComponent(); - DataContext = _viewModel; - } - - public static async Task Show(ControllerInputViewModel viewModel) - { - MotionInputView content = new(viewModel); - - ContentDialog contentDialog = new() - { - Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle], - PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], - SecondaryButtonText = "", - CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], - Content = content, - }; - contentDialog.PrimaryButtonClick += (sender, args) => - { - var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; - config.Slot = content._viewModel.Slot; - config.Sensitivity = content._viewModel.Sensitivity; - config.GyroDeadzone = content._viewModel.GyroDeadzone; - config.AltSlot = content._viewModel.AltSlot; - config.DsuServerHost = content._viewModel.DsuServerHost; - config.DsuServerPort = content._viewModel.DsuServerPort; - config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion; - config.MirrorInput = content._viewModel.MirrorInput; - }; - - await contentDialog.ShowAsync(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml deleted file mode 100644 index 5b7087a4..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml +++ /dev/null @@ -1,62 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" - x:DataType="viewModels:RumbleInputViewModel" - Focusable="True"> - <Grid Margin="10"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition /> - </Grid.RowDefinitions> - <StackPanel Orientation="Vertical"> - <StackPanel Orientation="Horizontal"> - <TextBlock - Width="100" - TextWrapping="WrapWithOverflow" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" /> - <controls:SliderScroll - Margin="0,-5,0,-5" - Width="200" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Maximum="10" - Minimum="0" - Value="{Binding StrongRumble, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Margin="5,0" - Text="{Binding StrongRumble, StringFormat=\{0:0.00\}}" /> - </StackPanel> - <StackPanel Orientation="Horizontal"> - <TextBlock - Width="100" - TextWrapping="WrapWithOverflow" - HorizontalAlignment="Center" - Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" /> - <controls:SliderScroll - Margin="0,-5,0,-5" - Width="200" - MaxWidth="200" - Maximum="10" - TickFrequency="0.01" - IsSnapToTickEnabled="True" - SmallChange="0.01" - Minimum="0" - Value="{Binding WeakRumble, Mode=TwoWay}" /> - <TextBlock - VerticalAlignment="Center" - Margin="5,0" - Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" /> - </StackPanel> - </StackPanel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs deleted file mode 100644 index 9307f872..00000000 --- a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Avalonia.Controls; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid.Controller; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Views.Input -{ - public partial class RumbleInputView : UserControl - { - private readonly RumbleInputViewModel _viewModel; - - public RumbleInputView() - { - InitializeComponent(); - } - - public RumbleInputView(ControllerInputViewModel viewModel) - { - var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; - - _viewModel = new RumbleInputViewModel - { - StrongRumble = config.StrongRumble, - WeakRumble = config.WeakRumble, - }; - - InitializeComponent(); - - DataContext = _viewModel; - } - - public static async Task Show(ControllerInputViewModel viewModel) - { - RumbleInputView content = new(viewModel); - - ContentDialog contentDialog = new() - { - Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle], - PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], - SecondaryButtonText = "", - CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], - Content = content, - }; - - contentDialog.PrimaryButtonClick += (sender, args) => - { - var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; - config.StrongRumble = content._viewModel.StrongRumble; - config.WeakRumble = content._viewModel.WeakRumble; - }; - - await contentDialog.ShowAsync(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml deleted file mode 100644 index 30358ada..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml +++ /dev/null @@ -1,203 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - mc:Ignorable="d" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - x:DataType="viewModels:MainWindowViewModel" - x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView"> - <Design.DataContext> - <viewModels:MainWindowViewModel /> - </Design.DataContext> - <DockPanel HorizontalAlignment="Stretch"> - <Menu - Name="Menu" - Height="35" - Margin="0" - HorizontalAlignment="Left"> - <Menu.ItemsPanel> - <ItemsPanelTemplate> - <DockPanel Margin="0" HorizontalAlignment="Stretch" /> - </ItemsPanelTemplate> - </Menu.ItemsPanel> - <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}"> - <MenuItem - Command="{Binding OpenFile}" - Header="{locale:Locale MenuBarFileOpenFromFile}" - IsEnabled="{Binding EnableNonGameRunningControls}" - ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> - <MenuItem - Command="{Binding OpenFolder}" - Header="{locale:Locale MenuBarFileOpenUnpacked}" - IsEnabled="{Binding EnableNonGameRunningControls}" - ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> - <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}"> - <MenuItem - Click="OpenMiiApplet" - Header="Mii Edit Applet" - ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> - </MenuItem> - <Separator /> - <MenuItem - Command="{Binding OpenRyujinxFolder}" - Header="{locale:Locale MenuBarFileOpenEmuFolder}" - ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" /> - <MenuItem - Command="{Binding OpenLogsFolder}" - Header="{locale:Locale MenuBarFileOpenLogsFolder}" - ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" /> - <Separator /> - <MenuItem - Click="CloseWindow" - Header="{locale:Locale MenuBarFileExit}" - ToolTip.Tip="{locale:Locale ExitTooltip}" /> - </MenuItem> - <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}"> - <MenuItem - Padding="-10,0,0,0" - Command="{Binding ToggleFullscreen}" - Header="{locale:Locale MenuBarOptionsToggleFullscreen}" - InputGesture="F11" /> - <MenuItem - Padding="0" - Command="{Binding ToggleStartGamesInFullscreen}" - Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"> - <MenuItem.Icon> - <CheckBox - MinWidth="{DynamicResource CheckBoxSize}" - MinHeight="{DynamicResource CheckBoxSize}" - IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" - Padding="0" /> - </MenuItem.Icon> - <MenuItem.Styles> - <Style Selector="Viewbox#PART_IconPresenter"> - <Setter Property="MaxHeight" Value="36" /> - <Setter Property="MinHeight" Value="36" /> - <Setter Property="MaxWidth" Value="36" /> - <Setter Property="MinWidth" Value="36" /> - </Style> - <Style Selector="ContentPresenter#PART_HeaderPresenter"> - <Setter Property="Padding" Value="-10,0,0,0" /> - </Style> - </MenuItem.Styles> - </MenuItem> - <MenuItem - Padding="0" - IsVisible="{Binding ShowConsoleVisible}" - Command="{Binding ToggleShowConsole}" - Header="{locale:Locale MenuBarOptionsShowConsole}"> - <MenuItem.Icon> - <CheckBox - MinWidth="{DynamicResource CheckBoxSize}" - MinHeight="{DynamicResource CheckBoxSize}" - IsChecked="{Binding ShowConsole, Mode=TwoWay}" - Padding="0" /> - </MenuItem.Icon> - <MenuItem.Styles> - <Style Selector="Viewbox#PART_IconPresenter"> - <Setter Property="MaxHeight" Value="36" /> - <Setter Property="MinHeight" Value="36" /> - <Setter Property="MaxWidth" Value="36" /> - <Setter Property="MinWidth" Value="36" /> - </Style> - <Style Selector="ContentPresenter#PART_HeaderPresenter"> - <Setter Property="Padding" Value="-10,0,0,0" /> - </Style> - </MenuItem.Styles> - </MenuItem> - <Separator /> - <MenuItem - Name="ChangeLanguageMenuItem" - Padding="-10,0,0,0" - Header="{locale:Locale MenuBarOptionsChangeLanguage}" /> - <MenuItem - Name="ToggleFileTypesMenuItem" - Padding="-10,0,0,0" - Header="{locale:Locale MenuBarShowFileTypes}" /> - <Separator /> - <MenuItem - Click="OpenSettings" - Padding="-10,0,0,0" - Header="{locale:Locale MenuBarOptionsSettings}" - ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> - <MenuItem - Command="{Binding ManageProfiles}" - Padding="-10,0,0,0" - Header="{locale:Locale MenuBarOptionsManageUserProfiles}" - IsEnabled="{Binding EnableNonGameRunningControls}" - ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> - </MenuItem> - <MenuItem - Name="ActionsMenuItem" - VerticalAlignment="Center" - Header="{locale:Locale MenuBarActions}" - IsEnabled="{Binding IsGameRunning}"> - <MenuItem - Click="PauseEmulation_Click" - Header="{locale:Locale MenuBarOptionsPauseEmulation}" - InputGesture="{Binding PauseKey}" - IsEnabled="{Binding !IsPaused}" - IsVisible="{Binding !IsPaused}" /> - <MenuItem - Click="ResumeEmulation_Click" - Header="{locale:Locale MenuBarOptionsResumeEmulation}" - InputGesture="{Binding PauseKey}" - IsEnabled="{Binding IsPaused}" - IsVisible="{Binding IsPaused}" /> - <MenuItem - Click="StopEmulation_Click" - Header="{locale:Locale MenuBarOptionsStopEmulation}" - InputGesture="Escape" - IsEnabled="{Binding IsGameRunning}" - ToolTip.Tip="{locale:Locale StopEmulationTooltip}" /> - <MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" /> - <Separator /> - <MenuItem - Name="ScanAmiiboMenuItem" - AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree" - Click="OpenAmiiboWindow" - Header="{locale:Locale MenuBarActionsScanAmiibo}" - IsEnabled="{Binding IsAmiiboRequested}" /> - <MenuItem - Command="{Binding TakeScreenshot}" - Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" - InputGesture="{Binding ScreenshotKey}" - IsEnabled="{Binding IsGameRunning}" /> - <MenuItem - Command="{Binding HideUi}" - Header="{locale:Locale MenuBarFileToolsHideUi}" - InputGesture="{Binding ShowUiKey}" - IsEnabled="{Binding IsGameRunning}" /> - <MenuItem - Click="OpenCheatManagerForCurrentApp" - Header="{locale:Locale GameListContextMenuManageCheat}" - IsEnabled="{Binding IsGameRunning}" /> - </MenuItem> - <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}"> - <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}"> - <MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" /> - <MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" /> - </MenuItem> - <MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}"> - <MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/> - <MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/> - </MenuItem> - </MenuItem> - <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}"> - <MenuItem - Name="UpdateMenuItem" - IsEnabled="{Binding CanUpdate}" - Click="CheckForUpdates" - Header="{locale:Locale MenuBarHelpCheckForUpdates}" - ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> - <Separator /> - <MenuItem - Click="OpenAboutWindow" - Header="{locale:Locale MenuBarHelpAbout}" - ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> - </MenuItem> - </Menu> - </DockPanel> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs deleted file mode 100644 index 8dff5086..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Interactivity; -using LibHac.Ncm; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -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 System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Ryujinx.Ava.UI.Views.Main -{ - public partial class MainMenuBarView : UserControl - { - public MainWindow Window { get; private set; } - public MainWindowViewModel ViewModel { get; private set; } - - public MainMenuBarView() - { - InitializeComponent(); - - ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems(); - ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems(); - } - - private CheckBox[] GenerateToggleFileTypeItems() - { - List<CheckBox> checkBoxes = new(); - - foreach (var item in Enum.GetValues(typeof(FileTypes))) - { - string fileName = Enum.GetName(typeof(FileTypes), item); - checkBoxes.Add(new CheckBox - { - Content = $".{fileName}", - IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes), - Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)), - }); - } - - return checkBoxes.ToArray(); - } - - private static MenuItem[] GenerateLanguageMenuItems() - { - List<MenuItem> menuItems = new(); - - string localePath = "Ryujinx.Ava/Assets/Locales"; - string localeExt = ".json"; - - string[] localesPath = EmbeddedResources.GetAllAvailableResources(localePath, localeExt); - - Array.Sort(localesPath); - - foreach (string locale in localesPath) - { - string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); - string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); - var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); - - if (!strings.TryGetValue("Language", out string languageName)) - { - languageName = languageCode; - } - - MenuItem menuItem = new() - { - Header = languageName, - Command = MiniCommand.Create(() => - { - MainWindowViewModel.ChangeLanguage(languageCode); - }), - }; - - menuItems.Add(menuItem); - } - - return menuItems.ToArray(); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - if (VisualRoot is MainWindow window) - { - Window = window; - } - - ViewModel = Window.ViewModel; - DataContext = ViewModel; - } - - private async void StopEmulation_Click(object sender, RoutedEventArgs e) - { - await Window.ViewModel.AppHost?.ShowExitPrompt(); - } - - private void PauseEmulation_Click(object sender, RoutedEventArgs e) - { - Window.ViewModel.AppHost?.Pause(); - } - - private void ResumeEmulation_Click(object sender, RoutedEventArgs e) - { - Window.ViewModel.AppHost?.Resume(); - } - - public async void OpenSettings(object sender, RoutedEventArgs e) - { - Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager); - - await Window.SettingsWindow.ShowDialog(Window); - - Window.SettingsWindow = null; - - ViewModel.LoadConfigurableHotKeys(); - } - - public async void OpenMiiApplet(object sender, RoutedEventArgs e) - { - string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); - - if (!string.IsNullOrEmpty(contentPath)) - { - await ViewModel.LoadApplication(contentPath, false, "Mii Applet"); - } - } - - public async void OpenAmiiboWindow(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsAmiiboRequested) - { - return; - } - - if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) - { - string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); - AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); - - await window.ShowDialog(Window); - - if (window.IsScanned) - { - ViewModel.ShowAll = window.ViewModel.ShowAllAmiibo; - ViewModel.LastScannedAmiiboId = window.ScannedAmiibo.GetId(); - - ViewModel.AppHost.Device.System.ScanAmiibo(deviceId, ViewModel.LastScannedAmiiboId, window.ViewModel.UseRandomUuid); - } - } - } - - public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsGameRunning) - { - return; - } - - string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); - - await new CheatWindow( - Window.VirtualFileSystem, - ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, - name, - Window.ViewModel.SelectedApplication.Path).ShowDialog(Window); - - ViewModel.AppHost.Device.EnableCheats(); - } - - private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) - { - if (sender is MenuItem) - { - ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _); - } - } - - private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) - { - if (FileAssociationHelper.Install()) - { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); - } - else - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); - } - } - - private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) - { - if (FileAssociationHelper.Uninstall()) - { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); - } - else - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); - } - } - - public async void CheckForUpdates(object sender, RoutedEventArgs e) - { - if (Updater.CanUpdate(true)) - { - await Updater.BeginParse(Window, true); - } - } - - public async void OpenAboutWindow(object sender, RoutedEventArgs e) - { - await AboutWindow.Show(); - } - - public void CloseWindow(object sender, RoutedEventArgs e) - { - Window.Close(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml deleted file mode 100644 index f9e192e6..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml +++ /dev/null @@ -1,289 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView" - x:DataType="viewModels:MainWindowViewModel"> - <Design.DataContext> - <viewModels:MainWindowViewModel /> - </Design.DataContext> - <Grid - Name="StatusBar" - Margin="0" - MinHeight="22" - HorizontalAlignment="Stretch" - VerticalAlignment="Bottom" - Background="{DynamicResource ThemeContentBackgroundColor}" - DockPanel.Dock="Bottom" - IsVisible="{Binding ShowMenuAndStatusBar}"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <StackPanel - Grid.Column="0" - Margin="5" - VerticalAlignment="Center" - IsVisible="{Binding EnableNonGameRunningControls}"> - <Grid Margin="0"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Button - Width="25" - Height="25" - MinWidth="0" - Margin="0,0,5,0" - VerticalAlignment="Center" - Background="Transparent" - Click="Refresh_OnClick"> - <ui:SymbolIcon - Width="50" - Height="100" - Symbol="Refresh" /> - </Button> - <TextBlock - Name="LoadStatus" - Grid.Column="1" - Margin="0,0,5,0" - VerticalAlignment="Center" - IsVisible="{Binding EnableNonGameRunningControls}" - Text="{locale:Locale StatusBarGamesLoaded}" /> - <ProgressBar - Name="LoadProgressBar" - Grid.Column="2" - Height="6" - VerticalAlignment="Center" - Foreground="{DynamicResource SystemAccentColorLight2}" - IsVisible="{Binding StatusBarVisible}" - Maximum="{Binding StatusBarProgressMaximum}" - Value="{Binding StatusBarProgressValue}" /> - </Grid> - </StackPanel> - <StackPanel - Grid.Column="1" - Margin="0,2" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding IsGameRunning}" - MaxHeight="18" - Orientation="Horizontal"> - <TextBlock - Name="VsyncStatus" - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Foreground="{Binding VsyncColor}" - IsVisible="{Binding !ShowLoadProgress}" - PointerReleased="VsyncStatus_PointerReleased" - Text="VSync" - TextAlignment="Start" /> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <TextBlock - Name="DockedStatus" - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding !ShowLoadProgress}" - PointerReleased="DockedStatus_PointerReleased" - Text="{Binding DockedStatusText}" - TextAlignment="Start" /> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <SplitButton - Name="AspectRatioStatus" - Padding="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Background="Transparent" - BorderThickness="0" - CornerRadius="0" - IsVisible="{Binding !ShowLoadProgress}" - Content="{Binding AspectRatioStatusText}" - Click="AspectRatioStatus_OnClick" - ToolTip.Tip="{locale:Locale AspectRatioTooltip}"> - <SplitButton.Styles> - <Style Selector="Border#SeparatorBorder"> - <Setter Property="Opacity" Value="0" /> - </Style> - </SplitButton.Styles> - <SplitButton.Flyout> - <MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/> - <MenuItem - Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" - Command="{Binding SetAspectRatio}" - CommandParameter="{x:Static config:AspectRatio.Stretched}"/> - </MenuFlyout> - </SplitButton.Flyout> - </SplitButton> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <ToggleSplitButton - Name="VolumeStatus" - Padding="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - VerticalContentAlignment="Center" - Content="{Binding VolumeStatusText}" - IsChecked="{Binding VolumeMuted}" - IsVisible="{Binding !ShowLoadProgress}" - PointerWheelChanged="VolumeStatus_OnPointerWheelChanged" - Background="Transparent" - BorderThickness="0" - CornerRadius="0"> - <ToggleSplitButton.Styles> - <Style Selector=":checked"> - <Style Selector="^:checked ContentPresenter"> - <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" /> - </Style> - </Style> - <Style Selector="Border#SeparatorBorder"> - <Setter Property="Opacity" Value="0" /> - </Style> - </ToggleSplitButton.Styles> - <ToggleSplitButton.Flyout> - <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> - <Grid Margin="0"> - <controls:SliderScroll - MaxHeight="40" - Width="150" - Margin="0" - Padding="0" - IsSnapToTickEnabled="True" - LargeChange="0.05" - Maximum="1" - Minimum="0" - SmallChange="0.01" - TickFrequency="0.05" - ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" - Value="{Binding Volume}" /> - </Grid> - </Flyout> - </ToggleSplitButton.Flyout> - </ToggleSplitButton> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <TextBlock - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding !ShowLoadProgress}" - Text="{Binding GameStatusText}" - TextAlignment="Start" /> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <TextBlock - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding !ShowLoadProgress}" - Text="{Binding FifoStatusText}" - TextAlignment="Start" /> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <TextBlock - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding !ShowLoadProgress}" - Text="{Binding BackendText}" - TextAlignment="Start" /> - <Border - Width="2" - Height="12" - Margin="0" - BorderBrush="Gray" - Background="Gray" - BorderThickness="1" - IsVisible="{Binding !ShowLoadProgress}" /> - <TextBlock - Margin="5,0,5,0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - IsVisible="{Binding !ShowLoadProgress}" - Text="{Binding GpuNameText}" - TextAlignment="Start" /> - </StackPanel> - <StackPanel - Grid.Column="3" - Margin="0,0,5,0" - VerticalAlignment="Center" - IsVisible="{Binding ShowFirmwareStatus}" - Orientation="Horizontal"> - <TextBlock - Name="FirmwareStatus" - Margin="0" - HorizontalAlignment="Right" - VerticalAlignment="Center" - Text="{locale:Locale StatusBarSystemVersion}" /> - </StackPanel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs deleted file mode 100644 index 239a7cbf..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.UI.Common.Configuration; -using System; - -namespace Ryujinx.Ava.UI.Views.Main -{ - public partial class MainStatusBarView : UserControl - { - public MainWindow Window; - - public MainStatusBarView() - { - InitializeComponent(); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - if (VisualRoot is MainWindow window) - { - Window = window; - } - - DataContext = Window.ViewModel; - } - - private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) - { - Window.ViewModel.AppHost.Device.EnableDeviceVsync = !Window.ViewModel.AppHost.Device.EnableDeviceVsync; - - Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); - } - - private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; - } - - private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e) - { - AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; - ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; - } - - private void Refresh_OnClick(object sender, RoutedEventArgs e) - { - Window.LoadApplications(); - } - - private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e) - { - // Change the volume by 5% at a time - float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f; - - Window.ViewModel.Volume = newValue switch - { - < 0 => 0, - > 1 => 1, - _ => newValue, - }; - - e.Handled = true; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml deleted file mode 100644 index cc21b5c6..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml +++ /dev/null @@ -1,177 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Views.Main.MainViewControls" - x:DataType="viewModels:MainWindowViewModel"> - <Design.DataContext> - <viewModels:MainWindowViewModel /> - </Design.DataContext> - <DockPanel - Margin="0,0,0,5" - Height="35" - HorizontalAlignment="Stretch"> - <Button - Width="40" - MinWidth="40" - Margin="5,2,0,2" - VerticalAlignment="Stretch" - Command="{Binding SetListMode}" - IsEnabled="{Binding IsGrid}"> - <ui:FontIcon - Margin="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - FontFamily="avares://FluentAvalonia/Fonts#Symbols" - Glyph="{helpers:GlyphValueConverter List}" /> - </Button> - <Button - Width="40" - MinWidth="40" - Margin="5,2,5,2" - VerticalAlignment="Stretch" - Command="{Binding SetGridMode}" - IsEnabled="{Binding IsList}"> - <ui:FontIcon - Margin="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - FontFamily="avares://FluentAvalonia/Fonts#Symbols" - Glyph="{helpers:GlyphValueConverter Grid}" /> - </Button> - <TextBlock - Margin="10,0" - VerticalAlignment="Center" - Text="{locale:Locale IconSize}" - ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> - <controls:SliderScroll - Width="150" - Height="35" - Margin="5,-10,5,0" - VerticalAlignment="Center" - IsSnapToTickEnabled="True" - SmallChange="1" - Maximum="4" - Minimum="1" - TickFrequency="1" - ToolTip.Tip="{locale:Locale IconSizeTooltip}" - Value="{Binding GridSizeScale}" /> - <CheckBox - Margin="0" - VerticalAlignment="Center" - IsChecked="{Binding ShowNames, Mode=TwoWay}" - IsVisible="{Binding IsGrid}"> - <TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" /> - </CheckBox> - <TextBox - Name="SearchBox" - MinWidth="200" - Margin="5,0,5,0" - HorizontalAlignment="Right" - VerticalAlignment="Center" - DockPanel.Dock="Right" - KeyUp="SearchBox_OnKeyUp" - Text="{Binding SearchText}" - Watermark="{locale:Locale MenuSearch}" /> - <DropDownButton - Width="150" - HorizontalAlignment="Right" - VerticalAlignment="Center" - Content="{Binding SortName}" - DockPanel.Dock="Right"> - <DropDownButton.Flyout> - <Flyout Placement="Bottom"> - <StackPanel - Margin="0" - HorizontalAlignment="Stretch" - Orientation="Vertical"> - <StackPanel> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale CommonFavorite}" - GroupName="Sort" - IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}" - Tag="Favorite" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderApplication}" - GroupName="Sort" - IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" - Tag="Title" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderDeveloper}" - GroupName="Sort" - IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}" - Tag="Developer" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderTimePlayed}" - GroupName="Sort" - IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}" - Tag="TotalTimePlayed" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderLastPlayed}" - GroupName="Sort" - IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}" - Tag="LastPlayed" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderFileExtension}" - GroupName="Sort" - IsChecked="{Binding IsSortedByType, Mode=OneTime}" - Tag="FileType" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderFileSize}" - GroupName="Sort" - IsChecked="{Binding IsSortedBySize, Mode=OneTime}" - Tag="FileSize" /> - <RadioButton - Checked="Sort_Checked" - Content="{locale:Locale GameListHeaderPath}" - GroupName="Sort" - IsChecked="{Binding IsSortedByPath, Mode=OneTime}" - Tag="Path" /> - </StackPanel> - <Border - Width="60" - Height="2" - Margin="5" - HorizontalAlignment="Stretch" - BorderBrush="White" - BorderThickness="0,1,0,0"> - <Separator Height="0" HorizontalAlignment="Stretch" /> - </Border> - <RadioButton - Checked="Order_Checked" - Content="{locale:Locale OrderAscending}" - GroupName="Order" - IsChecked="{Binding IsAscending, Mode=OneTime}" - Tag="Ascending" /> - <RadioButton - Checked="Order_Checked" - Content="{locale:Locale OrderDescending}" - GroupName="Order" - IsChecked="{Binding !IsAscending, Mode=OneTime}" - Tag="Descending" /> - </StackPanel> - </Flyout> - </DropDownButton.Flyout> - </DropDownButton> - <TextBlock - Margin="10,0" - HorizontalAlignment="Right" - VerticalAlignment="Center" - DockPanel.Dock="Right" - Text="{locale:Locale CommonSort}" /> - </DockPanel> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs deleted file mode 100644 index 02fd1bf5..00000000 --- a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ava.UI.Windows; -using System; - -namespace Ryujinx.Ava.UI.Views.Main -{ - public partial class MainViewControls : UserControl - { - public MainWindowViewModel ViewModel; - - public MainViewControls() - { - InitializeComponent(); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - if (VisualRoot is MainWindow window) - { - ViewModel = window.ViewModel; - } - - DataContext = ViewModel; - } - - public void Sort_Checked(object sender, RoutedEventArgs args) - { - if (sender is RadioButton button) - { - ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString())); - } - } - - public void Order_Checked(object sender, RoutedEventArgs args) - { - if (sender is RadioButton button) - { - ViewModel.Sort(button.Tag.ToString() != "Descending"); - } - } - - private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) - { - ViewModel.SearchText = SearchBox.Text; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml deleted file mode 100644 index 657e07ee..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml +++ /dev/null @@ -1,81 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsAudioView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="AudioPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" /> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemAudioBackend}" - ToolTip.Tip="{locale:Locale AudioBackendTooltip}" - Width="250" /> - <ComboBox SelectedIndex="{Binding AudioBackend}" - Width="350" - HorizontalContentAlignment="Left"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" /> - </ComboBoxItem> - <ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}"> - <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" /> - </ComboBoxItem> - <ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}"> - <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" /> - </ComboBoxItem> - <ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}"> - <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemAudioVolume}" - ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" - Width="250" /> - <ui:NumberBox Value="{Binding Volume}" - ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" - Width="350" - SmallChange="1" - LargeChange="10" - SimpleNumberFormat="F0" - SpinButtonPlacementMode="Inline" - Minimum="0" - Maximum="100" /> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <controls:SliderScroll Value="{Binding Volume}" - Margin="250,0,0,0" - ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" - Minimum="0" - Maximum="100" - SmallChange="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - LargeChange="10" - Width="350" /> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs deleted file mode 100644 index b672a0f2..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsAudioView : UserControl - { - public SettingsAudioView() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml deleted file mode 100644 index c74d3dd5..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml +++ /dev/null @@ -1,77 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="CpuPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" /> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical"> - <CheckBox IsChecked="{Binding EnablePptc}"> - <TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}" - ToolTip.Tip="{locale:Locale PptcToggleTooltip}" /> - </CheckBox> - </StackPanel> - <Separator Height="1" /> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" /> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemMemoryManagerMode}" - ToolTip.Tip="{locale:Locale MemoryManagerTooltip}" - Width="250" /> - <ComboBox SelectedIndex="{Binding MemoryMode}" - ToolTip.Tip="{locale:Locale MemoryManagerTooltip}" - HorizontalContentAlignment="Left" - Width="350"> - <ComboBoxItem - ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}"> - <TextBlock - Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" /> - </ComboBoxItem> - <ComboBoxItem - ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" /> - </ComboBoxItem> - <ComboBoxItem - ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}"> - <TextBlock - Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <CheckBox IsChecked="{Binding UseHypervisor}" - IsVisible="{Binding IsHypervisorAvailable}" - ToolTip.Tip="{locale:Locale UseHypervisorTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabSystemUseHypervisor}" - ToolTip.Tip="{locale:Locale UseHypervisorTooltip}" /> - </CheckBox> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs deleted file mode 100644 index a475971a..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsCPUView : UserControl - { - public SettingsCPUView() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml deleted file mode 100644 index 22449478..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml +++ /dev/null @@ -1,301 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - Design.Width="1000" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="GraphicsPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" /> - <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}" - Text="{locale:Locale SettingsTabGraphicsBackend}" - Width="250" /> - <ComboBox Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}" - SelectedIndex="{Binding GraphicsBackendIndex}"> - <ComboBoxItem IsVisible="{Binding IsVulkanAvailable}"> - <TextBlock Text="Vulkan" /> - </ComboBoxItem> - <ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}"> - <TextBlock Text="OpenGL" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}" - Text="{locale:Locale SettingsTabGraphicsPreferredGpu}" - Width="250" /> - <ComboBox Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}" - SelectedIndex="{Binding PreferredGpuIndex}" - ItemsSource="{Binding AvailableGpus}"/> - </StackPanel> - </StackPanel> - <Separator Height="1" /> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" /> - <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10"> - <StackPanel Orientation="Vertical"> - <CheckBox IsChecked="{Binding EnableShaderCache}" - ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableTextureRecompression}" - ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}"> - <TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableMacroHLE}" - ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}"> - <TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableColorSpacePassthrough}" - IsVisible="{Binding ColorSpacePassthroughAvailable}" - ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}"> - <TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" /> - </CheckBox> - </StackPanel> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}" - Text="{locale:Locale SettingsTabGraphicsResolutionScale}" - Width="250" /> - <ComboBox SelectedIndex="{Binding ResolutionScale}" - Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" /> - </ComboBoxItem> - </ComboBox> - <ui:NumberBox - Margin="10,0,0,0" - ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}" - MinWidth="150" - SmallChange="0.1" - LargeChange="1" - SimpleNumberFormat="F2" - SpinButtonPlacementMode="Inline" - IsVisible="{Binding IsCustomResolutionScaleActive}" - Maximum="100" - Minimum="0.1" - Value="{Binding CustomResolutionScale}" /> - </StackPanel> - <StackPanel - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale GraphicsAATooltip}" - Text="{locale:Locale GraphicsAALabel}" - Width="250" /> - <ComboBox Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale GraphicsAATooltip}" - SelectedIndex="{Binding AntiAliasingEffect}"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="FXAA" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SmaaLow}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SmaaMedium}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SmaaHigh}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SmaaUltra}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - </StackPanel> - <StackPanel - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" - Text="{locale:Locale GraphicsScalingFilterLabel}" - Width="250" /> - <ComboBox Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" - SelectedIndex="{Binding ScalingFilter}"> - <ComboBoxItem> - <TextBlock Text="Bilinear" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="Nearest" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="FSR" /> - </ComboBoxItem> - </ComboBox> - <controls:SliderScroll Value="{Binding ScalingFilterLevel}" - ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}" - MinWidth="150" - Margin="10,-3,0,0" - Height="32" - Padding="0,-5" - IsVisible="{Binding IsScalingFilterActive}" - TickFrequency="1" - IsSnapToTickEnabled="True" - LargeChange="10" - SmallChange="1" - VerticalAlignment="Center" - Minimum="0" - Maximum="100" /> - <TextBlock Margin="5,0" - Width="40" - IsVisible="{Binding IsScalingFilterActive}" - Text="{Binding ScalingFilterLevelText}"/> - </StackPanel> - </StackPanel> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale AnisotropyTooltip}" - Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}" - Width="250" /> - <ComboBox SelectedIndex="{Binding MaxAnisotropy}" - Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale AnisotropyTooltip}"> - <ComboBoxItem> - <TextBlock - Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock - Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale AspectRatioTooltip}" - Text="{locale:Locale SettingsTabGraphicsAspectRatio}" - Width="250" /> - <ComboBox SelectedIndex="{Binding AspectRatio}" - Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale AspectRatioTooltip}"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - </StackPanel> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}" - Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}" - Width="250" /> - <ComboBox Width="350" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale GalThreadingTooltip}" - SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale CommonAuto}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale CommonOff}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale CommonOn}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - </StackPanel> - <Separator Height="1" /> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" /> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <StackPanel Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" - Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}" - Width="250" /> - <TextBox Text="{Binding ShaderDumpPath}" - Width="350" - ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" /> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs deleted file mode 100644 index 67341330..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsGraphicsView : UserControl - { - public SettingsGraphicsView() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml deleted file mode 100644 index b4eae01e..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml +++ /dev/null @@ -1,103 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHotkeysView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel" - Focusable="True"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <UserControl.Resources> - <helpers:KeyValueConverter x:Key="Key" /> - </UserControl.Resources> - <ScrollViewer - Name="HotkeysPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel Margin="10" Orientation="Vertical" Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" /> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.ShowUI, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" /> - <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> - <TextBlock - Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}" - TextAlignment="Center" /> - </ToggleButton> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs deleted file mode 100644 index b006d703..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Interactivity; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Input; -using Ryujinx.Input.Assigner; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsHotkeysView : UserControl - { - private ButtonKeyAssigner _currentAssigner; - private readonly IGamepadDriver _avaloniaKeyboardDriver; - - public SettingsHotkeysView() - { - InitializeComponent(); - _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); - } - - private void MouseClick(object sender, PointerPressedEventArgs e) - { - bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; - - _currentAssigner?.Cancel(shouldUnbind); - - PointerPressed -= MouseClick; - } - - private void Button_Checked(object sender, RoutedEventArgs e) - { - if (sender is ToggleButton button) - { - if (_currentAssigner != null && button == _currentAssigner.ToggledButton) - { - return; - } - - if (_currentAssigner == null && button.IsChecked != null && (bool)button.IsChecked) - { - _currentAssigner = new ButtonKeyAssigner(button); - - this.Focus(NavigationMethod.Pointer); - - PointerPressed += MouseClick; - - var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad(_avaloniaKeyboardDriver.GamepadsIds[0]); - IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); - - _currentAssigner.GetInputAndAssign(assigner); - } - else - { - if (_currentAssigner != null) - { - ToggleButton oldButton = _currentAssigner.ToggledButton; - - _currentAssigner.Cancel(); - _currentAssigner = null; - - button.IsChecked = false; - } - } - } - } - - private void Button_Unchecked(object sender, RoutedEventArgs e) - { - _currentAssigner?.Cancel(); - _currentAssigner = null; - } - - public void Dispose() - { - _currentAssigner?.Cancel(); - _currentAssigner = null; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml deleted file mode 100644 index 81f4b68b..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml +++ /dev/null @@ -1,67 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="InputPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <Panel - Margin="10"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto"/> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <views:ControllerInputView - Grid.Row="0" - Name="ControllerSettings" /> - <StackPanel - Orientation="Vertical" - Grid.Row="2"> - <Separator - Margin="0 10" - Height="1" /> - <StackPanel - Orientation="Horizontal" - Spacing="10"> - <CheckBox - ToolTip.Tip="{locale:Locale DockModeToggleTooltip}" - MinWidth="0" - IsChecked="{Binding EnableDockedMode}"> - <TextBlock - Text="{locale:Locale SettingsTabInputEnableDockedMode}" /> - </CheckBox> - <CheckBox - ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}" - IsChecked="{Binding EnableKeyboard}"> - <TextBlock - Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" /> - </CheckBox> - <CheckBox - ToolTip.Tip="{locale:Locale DirectMouseTooltip}" - IsChecked="{Binding EnableMouse}"> - <TextBlock - Text="{locale:Locale SettingsTabInputDirectMouseAccess}" /> - </CheckBox> - </StackPanel> - </StackPanel> - </Grid> - </Panel> - </Border> - </ScrollViewer> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs deleted file mode 100644 index e75c9f0c..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsInputView : UserControl - { - public SettingsInputView() - { - InitializeComponent(); - } - - public void Dispose() - { - ControllerSettings.Dispose(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml deleted file mode 100644 index 0fc9ea1b..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml +++ /dev/null @@ -1,120 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsLoggingView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="LoggingPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" /> - <StackPanel Margin="10,0,0,0" Orientation="Vertical"> - <CheckBox IsChecked="{Binding EnableFileLog}" - ToolTip.Tip="{locale:Locale FileLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableStub}" - ToolTip.Tip="{locale:Locale StubLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableInfo}" - ToolTip.Tip="{locale:Locale InfoLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableWarn}" - ToolTip.Tip="{locale:Locale WarnLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableError}" - ToolTip.Tip="{locale:Locale ErrorLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableGuest}" - ToolTip.Tip="{locale:Locale GuestLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" /> - </CheckBox> - </StackPanel> - <Separator Height="1" /> - <StackPanel Orientation="Vertical" Spacing="2"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" /> - <TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" /> - </StackPanel> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <StackPanel Orientation="Vertical"> - <CheckBox IsChecked="{Binding EnableTrace}" - ToolTip.Tip="{locale:Locale TraceLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableFsAccessLog}" - ToolTip.Tip="{locale:Locale FileAccessLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableDebug}" - ToolTip.Tip="{locale:Locale DebugLogTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" /> - </CheckBox> - <StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}" - Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}" - Width="285" /> - <ui:NumberBox - Maximum="3" - Minimum="0" - Width="150" - SpinButtonPlacementMode="Inline" - SmallChange="1" - LargeChange="1" - Value="{Binding FsGlobalAccessLogMode}" /> - </StackPanel> - <StackPanel Margin="0,10,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}" - ToolTip.Tip="{locale:Locale OpenGlLogLevel}" - Width="285" /> - <ComboBox SelectedIndex="{Binding OpenglDebugLevel}" - Width="150" - HorizontalContentAlignment="Left" - ToolTip.Tip="{locale:Locale OpenGlLogLevel}"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock - Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - </StackPanel> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs deleted file mode 100644 index c8df46b3..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsLoggingView : UserControl - { - public SettingsLoggingView() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml deleted file mode 100644 index 9bb81463..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml +++ /dev/null @@ -1,58 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="NetworkPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkMultiplayer}" /> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale MultiplayerMode}" - ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}" - Width="200" /> - <ComboBox SelectedIndex="{Binding MultiplayerModeIndex}" - ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}" - HorizontalContentAlignment="Left" - ItemsSource="{Binding MultiplayerModes}" - Width="250" /> - </StackPanel> - <Separator Height="1" /> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" /> - <CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}"> - <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}" - ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> - </CheckBox> - <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabNetworkInterface}" - ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" - Width="200" /> - <ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}" - ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" - HorizontalContentAlignment="Left" - ItemsSource="{Binding NetworkInterfaceList}" - Width="250" /> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs deleted file mode 100644 index b771933e..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Controls; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsNetworkView : UserControl - { - public SettingsNetworkView() - { - InitializeComponent(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml deleted file mode 100644 index e6f7c6e4..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml +++ /dev/null @@ -1,224 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsSystemView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <UserControl.Resources> - <helpers:TimeZoneConverter x:Key="TimeZone" /> - </UserControl.Resources> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="SystemPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock - Classes="h1" - Text="{locale:Locale SettingsTabSystemCore}" /> - <StackPanel - Margin="10,0,0,0" - Orientation="Vertical"> - <StackPanel - Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock - VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemSystemRegion}" - Width="250" /> - <ComboBox - SelectedIndex="{Binding Region}" - ToolTip.Tip="{locale:Locale RegionTooltip}" - HorizontalContentAlignment="Left" - Width="350"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel - Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock - VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemSystemLanguage}" - ToolTip.Tip="{locale:Locale LanguageTooltip}" - Width="250" /> - <ComboBox - SelectedIndex="{Binding Language}" - ToolTip.Tip="{locale:Locale LanguageTooltip}" - HorizontalContentAlignment="Left" - Width="350"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel - Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock - VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemSystemTimeZone}" - ToolTip.Tip="{locale:Locale TimezoneTooltip}" - Width="250" /> - <AutoCompleteBox - Name="TimeZoneBox" - Width="350" - MaxDropDownHeight="500" - FilterMode="Contains" - ItemsSource="{Binding TimeZones}" - SelectionChanged="TimeZoneBox_OnSelectionChanged" - Text="{Binding Path=TimeZone, Mode=OneWay}" - TextChanged="TimeZoneBox_OnTextChanged" - ToolTip.Tip="{locale:Locale TimezoneTooltip}" - ValueMemberBinding="{Binding Mode=OneWay, Converter={StaticResource TimeZone}}" /> - </StackPanel> - <StackPanel - Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock - VerticalAlignment="Center" - Text="{locale:Locale SettingsTabSystemSystemTime}" - ToolTip.Tip="{locale:Locale TimeTooltip}" - Width="250"/> - <DatePicker - VerticalAlignment="Center" - SelectedDate="{Binding CurrentDate}" - ToolTip.Tip="{locale:Locale TimeTooltip}" - Width="350" /> - </StackPanel> - <StackPanel - Margin="250,0,0,10" - Orientation="Horizontal"> - <TimePicker - VerticalAlignment="Center" - ClockIdentifier="24HourClock" - SelectedTime="{Binding CurrentTime}" - Width="350" - ToolTip.Tip="{locale:Locale TimeTooltip}" /> - </StackPanel> - <CheckBox IsChecked="{Binding EnableVsync}"> - <TextBlock - Text="{locale:Locale SettingsTabSystemEnableVsync}" - ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" /> - </CheckBox> - <CheckBox IsChecked="{Binding EnableFsIntegrityChecks}"> - <TextBlock - Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}" - ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" /> - </CheckBox> - </StackPanel> - <Separator Height="1" /> - <StackPanel - Orientation="Vertical" - Spacing="2"> - <TextBlock - Classes="h1" - Text="{locale:Locale SettingsTabSystemHacks}" /> - <TextBlock - Foreground="{DynamicResource SecondaryTextColor}" - Text="{locale:Locale SettingsTabSystemHacksNote}" /> - </StackPanel> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical"> - <CheckBox - IsChecked="{Binding ExpandDramSize}" - ToolTip.Tip="{locale:Locale DRamTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" /> - </CheckBox> - <CheckBox - IsChecked="{Binding IgnoreMissingServices}" - ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}"> - <TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" /> - </CheckBox> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs deleted file mode 100644 index 2c9eac28..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Avalonia.Controls; -using Ryujinx.Ava.UI.ViewModels; -using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsSystemView : UserControl - { - public SettingsViewModel ViewModel; - - public SettingsSystemView() - { - InitializeComponent(); - } - - private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (e.AddedItems != null && e.AddedItems.Count > 0) - { - if (e.AddedItems[0] is TimeZone timeZone) - { - e.Handled = true; - - ViewModel.ValidateAndSetTimeZone(timeZone.Location); - } - } - } - - private void TimeZoneBox_OnTextChanged(object sender, TextChangedEventArgs e) - { - if (sender is AutoCompleteBox box && box.SelectedItem is TimeZone timeZone) - { - ViewModel.ValidateAndSetTimeZone(timeZone.Location); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml deleted file mode 100644 index 6504637e..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml +++ /dev/null @@ -1,128 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUiView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - x:DataType="viewModels:SettingsViewModel"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <ScrollViewer - Name="UiPage" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto"> - <Border Classes="settings"> - <StackPanel - Margin="10" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" /> - <StackPanel Margin="10,0,0,0" Orientation="Vertical"> - <CheckBox IsChecked="{Binding EnableDiscordIntegration}"> - <TextBlock VerticalAlignment="Center" - ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}" - Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> - </CheckBox> - <CheckBox IsChecked="{Binding CheckUpdatesOnStart}"> - <TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" /> - </CheckBox> - <CheckBox IsChecked="{Binding ShowConfirmExit}"> - <TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" /> - </CheckBox> - <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> - <TextBlock VerticalAlignment="Center" - Text="{locale:Locale SettingsTabGeneralHideCursor}" - Width="150" /> - <ComboBox SelectedIndex="{Binding HideCursor}" - HorizontalContentAlignment="Left" - MinWidth="100"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorNever}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorAlways}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - <StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal"> - <TextBlock - VerticalAlignment="Center" - Text="{locale:Locale SettingsTabGeneralTheme}" - Width="150" /> - <ComboBox SelectedIndex="{Binding BaseStyleIndex}" - HorizontalContentAlignment="Left" - MinWidth="100"> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGeneralThemeLight}" /> - </ComboBoxItem> - <ComboBoxItem> - <TextBlock Text="{locale:Locale SettingsTabGeneralThemeDark}" /> - </ComboBoxItem> - </ComboBox> - </StackPanel> - </StackPanel> - <Separator Height="1" /> - <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" /> - <StackPanel - Margin="10,0,0,0" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <ListBox - Name="GameList" - MinHeight="230" - ItemsSource="{Binding GameDirectories}"> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Padding" Value="10" /> - <Setter Property="Background" Value="{DynamicResource ListBoxBackground}" /> - </Style> - </ListBox.Styles> - </ListBox> - <Grid HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <TextBox - Name="PathBox" - Margin="0" - ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}" - VerticalAlignment="Stretch" /> - <Button - Name="AddButton" - Grid.Column="1" - MinWidth="90" - Margin="10,0,0,0" - ToolTip.Tip="{locale:Locale AddGameDirTooltip}" - Click="AddButton_OnClick"> - <TextBlock HorizontalAlignment="Center" - Text="{locale:Locale SettingsTabGeneralAdd}" /> - </Button> - <Button - Name="RemoveButton" - Grid.Column="2" - MinWidth="90" - Margin="10,0,0,0" - ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}" - Click="RemoveButton_OnClick"> - <TextBlock HorizontalAlignment="Center" - Text="{locale:Locale SettingsTabGeneralRemove}" /> - </Button> - </Grid> - </StackPanel> - </StackPanel> - </Border> - </ScrollViewer> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs deleted file mode 100644 index 996d15cd..00000000 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; -using Avalonia.VisualTree; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.ViewModels; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Ryujinx.Ava.UI.Views.Settings -{ - public partial class SettingsUiView : UserControl - { - public SettingsViewModel ViewModel; - - public SettingsUiView() - { - InitializeComponent(); - } - - private async void AddButton_OnClick(object sender, RoutedEventArgs e) - { - string path = PathBox.Text; - - if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path)) - { - ViewModel.GameDirectories.Add(path); - ViewModel.DirectoryChanged = true; - } - else - { - if (this.GetVisualRoot() is Window window) - { - var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - AllowMultiple = false, - }); - - if (result.Count > 0) - { - ViewModel.GameDirectories.Add(result[0].Path.LocalPath); - ViewModel.DirectoryChanged = true; - } - } - } - } - - private void RemoveButton_OnClick(object sender, RoutedEventArgs e) - { - int oldIndex = GameList.SelectedIndex; - - foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>())) - { - ViewModel.GameDirectories.Remove(path); - ViewModel.DirectoryChanged = true; - } - - if (GameList.ItemCount > 0) - { - GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0; - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml deleted file mode 100644 index ab83c2cd..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml +++ /dev/null @@ -1,122 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.User.UserEditorView" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - Margin="0" - MinWidth="500" - Padding="0" - mc:Ignorable="d" - Focusable="True" - x:DataType="models:TempProfile"> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - </UserControl.Resources> - <Grid Margin="0"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <StackPanel - Grid.Row="0" - Grid.Column="0" - HorizontalAlignment="Stretch" - Orientation="Vertical" - Spacing="10"> - <TextBlock Text="{locale:Locale UserProfilesName}" /> - <TextBox - Name="NameBox" - Width="300" - HorizontalAlignment="Stretch" - MaxLength="{Binding MaxProfileNameLength}" - Watermark="{locale:Locale ProfileNameSelectionWatermark}" - Text="{Binding Name}" /> - <TextBlock Name="IdText" Text="{locale:Locale UserProfilesUserId}" /> - <TextBox - Name="IdLabel" - Width="300" - HorizontalAlignment="Stretch" - IsReadOnly="True" - Text="{Binding UserIdString}" /> - </StackPanel> - <StackPanel - Grid.Row="0" - Grid.Column="1" - HorizontalAlignment="Right" - VerticalAlignment="Stretch" - Orientation="Vertical"> - <Border - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1"> - <Panel> - <ui:SymbolIcon - FontSize="60" - Width="96" - Height="96" - Margin="0" - Foreground="{DynamicResource AppListHoverBackgroundColor}" - HorizontalAlignment="Stretch" - VerticalAlignment="Top" - Symbol="Camera" /> - <Image - Name="ProfileImage" - Width="96" - Height="96" - Margin="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Top" - Source="{Binding Image, Converter={StaticResource ByteImage}}" /> - </Panel> - </Border> - </StackPanel> - <StackPanel - Grid.Row="1" - Grid.Column="0" - Grid.ColumnSpan="2" - HorizontalAlignment="Left" - Orientation="Horizontal" - Margin="0 24 0 0" - Spacing="10"> - <Button - Width="50" - MinWidth="50" - Click="BackButton_Click"> - <ui:SymbolIcon Symbol="Back" /> - </Button> - </StackPanel> - <StackPanel - Grid.Row="1" - Grid.Column="0" - Grid.ColumnSpan="2" - HorizontalAlignment="Right" - Orientation="Horizontal" - Margin="0 24 0 0" - Spacing="10"> - <Button - Name="DeleteButton" - Click="DeleteButton_Click" - Content="{locale:Locale UserProfilesDelete}" /> - <Button - Name="ChangePictureButton" - Click="ChangePictureButton_Click" - Content="{locale:Locale UserProfilesChangeProfileImage}" /> - <Button - Name="AddPictureButton" - Click="ChangePictureButton_Click" - Content="{locale:Locale UserProfilesSetProfileImage}" /> - <Button - Name="SaveButton" - Click="SaveButton_Click" - Content="{locale:Locale Save}" /> - </StackPanel> - </Grid> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs deleted file mode 100644 index 588fa471..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Interactivity; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System; -using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserEditorView : UserControl - { - private NavigationDialogHost _parent; - private UserProfile _profile; - private bool _isNewUser; - - public TempProfile TempProfile { get; set; } - public static uint MaxProfileNameLength => 0x20; - public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId; - - public UserEditorView() - { - InitializeComponent(); - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - switch (arg.NavigationMode) - { - case NavigationMode.New: - var (parent, profile, isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter; - _isNewUser = isNewUser; - _profile = profile; - TempProfile = new TempProfile(_profile); - - _parent = parent; - break; - } - - ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " + - $"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}"; - - DataContext = TempProfile; - - AddPictureButton.IsVisible = _isNewUser; - ChangePictureButton.IsVisible = !_isNewUser; - IdLabel.IsVisible = _profile != null; - IdText.IsVisible = _profile != null; - if (!_isNewUser && IsDeletable) - { - DeleteButton.IsVisible = true; - } - else - { - DeleteButton.IsVisible = false; - } - } - } - - private async void BackButton_Click(object sender, RoutedEventArgs e) - { - if (_isNewUser) - { - if (TempProfile.Name != String.Empty || TempProfile.Image != null) - { - if (await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage], - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage])) - { - _parent?.GoBack(); - } - } - else - { - _parent?.GoBack(); - } - } - else - { - if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image) - { - if (await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage], - LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage])) - { - _parent?.GoBack(); - } - } - else - { - _parent?.GoBack(); - } - } - } - - private void DeleteButton_Click(object sender, RoutedEventArgs e) - { - _parent.DeleteUser(_profile); - } - - private void SaveButton_Click(object sender, RoutedEventArgs e) - { - DataValidationErrors.ClearErrors(NameBox); - - if (string.IsNullOrWhiteSpace(TempProfile.Name)) - { - DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError])); - - return; - } - - if (TempProfile.Image == null) - { - _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); - - return; - } - - if (_profile != null && !_isNewUser) - { - _profile.Name = TempProfile.Name; - _profile.Image = TempProfile.Image; - _profile.UpdateState(); - _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name); - _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image); - } - else if (_isNewUser) - { - _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId); - } - else - { - return; - } - - _parent?.GoBack(); - } - - public void SelectProfileImage() - { - _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); - } - - private void ChangePictureButton_Click(object sender, RoutedEventArgs e) - { - if (_profile != null || _isNewUser) - { - SelectProfileImage(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml deleted file mode 100644 index 21dfc909..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml +++ /dev/null @@ -1,113 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - Width="528" - d:DesignWidth="578" - d:DesignHeight="350" - x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel" - Focusable="True"> - <Design.DataContext> - <viewModels:UserFirmwareAvatarSelectorViewModel /> - </Design.DataContext> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - </UserControl.Resources> - <Grid - Margin="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <ListBox - Grid.Row="1" - BorderThickness="0" - SelectedIndex="{Binding SelectedIndex}" - Height="400" - ItemsSource="{Binding Images}" - HorizontalAlignment="Stretch" - VerticalAlignment="Center"> - <ListBox.ItemsPanel> - <ItemsPanelTemplate> - <WrapPanel - Orientation="Horizontal" - Margin="0" - HorizontalAlignment="Center" /> - </ItemsPanelTemplate> - </ListBox.ItemsPanel> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="CornerRadius" Value="4" /> - <Setter Property="Width" Value="85" /> - <Setter Property="MaxWidth" Value="85" /> - <Setter Property="MinWidth" Value="85" /> - </Style> - <Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator"> - <Setter Property="MinHeight" Value="70" /> - </Style> - </ListBox.Styles> - <ListBox.ItemTemplate> - <DataTemplate> - <Panel - Background="{Binding BackgroundColor}" - Margin="5"> - <Image Source="{Binding Data, Converter={StaticResource ByteImage}}" /> - </Panel> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - <StackPanel - Grid.Row="3" - Orientation="Horizontal" - Spacing="10" - Margin="0 24 0 0" - HorizontalAlignment="Left"> - <Button - Width="50" - MinWidth="50" - Height="35" - Click="GoBack"> - <ui:SymbolIcon Symbol="Back" /> - </Button> - </StackPanel> - <StackPanel - Grid.Row="3" - Orientation="Horizontal" - Spacing="10" - Margin="0 24 0 0" - HorizontalAlignment="Right"> - <ui:ColorPickerButton - FlyoutPlacement="Top" - IsMoreButtonVisible="False" - UseColorPalette="False" - UseColorTriangle="False" - UseColorWheel="False" - ShowAcceptDismissButtons="False" - IsAlphaEnabled="False" - Color="{Binding BackgroundColor, Mode=TwoWay}" - Name="ColorButton"> - <ui:ColorPickerButton.Styles> - <Style Selector="Grid#Root > DockPanel > Grid"> - <Setter Property="IsVisible" Value="False" /> - </Style> - </ui:ColorPickerButton.Styles> - </ui:ColorPickerButton> - <Button - Content="{locale:Locale AvatarChoose}" - Height="35" - Name="ChooseButton" - Click="ChooseButton_OnClick" /> - </StackPanel> - </Grid> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs deleted file mode 100644 index b6376866..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.HLE.FileSystem; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System.IO; -using Image = SixLabors.ImageSharp.Image; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserFirmwareAvatarSelectorView : UserControl - { - private NavigationDialogHost _parent; - private TempProfile _profile; - - public UserFirmwareAvatarSelectorView(ContentManager contentManager) - { - ContentManager = contentManager; - - DataContext = ViewModel; - - InitializeComponent(); - } - - public UserFirmwareAvatarSelectorView() - { - InitializeComponent(); - - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - if (arg.NavigationMode == NavigationMode.New) - { - (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; - ContentManager = _parent.ContentManager; - if (Program.PreviewerDetached) - { - ViewModel = new UserFirmwareAvatarSelectorViewModel(); - } - - DataContext = ViewModel; - } - } - } - - public ContentManager ContentManager { get; private set; } - - internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; } - - private void GoBack(object sender, RoutedEventArgs e) - { - _parent.GoBack(); - } - - private void ChooseButton_OnClick(object sender, RoutedEventArgs e) - { - if (ViewModel.SelectedImage != null) - { - MemoryStream streamJpg = new(); - Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder()); - - avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( - ViewModel.BackgroundColor.R, - ViewModel.BackgroundColor.G, - ViewModel.BackgroundColor.B, - ViewModel.BackgroundColor.A))); - avatarImage.SaveAsJpeg(streamJpg); - - _profile.Image = streamJpg.ToArray(); - - _parent.GoBack(); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml deleted file mode 100644 index b1786430..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml +++ /dev/null @@ -1,62 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels" - Focusable="True" - mc:Ignorable="d" - x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView" - x:DataType="viewModles:UserProfileImageSelectorViewModel" - Width="500" - d:DesignWidth="500"> - <Design.DataContext> - <viewModles:UserProfileImageSelectorViewModel /> - </Design.DataContext> - <Grid - HorizontalAlignment="Stretch" - VerticalAlignment="Center"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="70" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <TextBlock - Grid.Row="0" - TextWrapping="Wrap" - HorizontalAlignment="Left" - TextAlignment="Start" - Text="{locale:Locale ProfileImageSelectionNote}" /> - <StackPanel - Grid.Row="2" - Spacing="10" - HorizontalAlignment="Left" - Orientation="Horizontal"> - <Button - Width="50" - MinWidth="50" - Click="GoBack"> - <ui:SymbolIcon Symbol="Back" /> - </Button> - </StackPanel> - <StackPanel - Grid.Row="2" - Spacing="10" - HorizontalAlignment="Right" - Orientation="Horizontal"> - <Button - Name="Import" - Click="Import_OnClick"> - <TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" /> - </Button> - <Button - Name="SelectFirmwareImage" - IsEnabled="{Binding FirmwareFound}" - Click="SelectFirmwareImage_OnClick"> - <TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" /> - </Button> - </StackPanel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs deleted file mode 100644 index fabfaa4e..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; -using Avalonia.VisualTree; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.HLE.FileSystem; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; -using System.Collections.Generic; -using System.IO; -using Image = SixLabors.ImageSharp.Image; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserProfileImageSelectorView : UserControl - { - private ContentManager _contentManager; - private NavigationDialogHost _parent; - private TempProfile _profile; - - internal UserProfileImageSelectorViewModel ViewModel { get; private set; } - - public UserProfileImageSelectorView() - { - InitializeComponent(); - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - switch (arg.NavigationMode) - { - case NavigationMode.New: - (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; - _contentManager = _parent.ContentManager; - - ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}"; - - if (Program.PreviewerDetached) - { - DataContext = ViewModel = new UserProfileImageSelectorViewModel(); - ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null; - } - - break; - case NavigationMode.Back: - if (_profile.Image != null) - { - _parent.GoBack(); - } - break; - } - } - } - - private async void Import_OnClick(object sender, RoutedEventArgs e) - { - var window = this.GetVisualRoot() as Window; - var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - AllowMultiple = false, - FileTypeFilter = new List<FilePickerFileType> - { - new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) - { - Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" }, - AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" }, - MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }, - }, - }, - }); - - if (result.Count > 0) - { - _profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath)); - _parent.GoBack(); - } - } - - private void GoBack(object sender, RoutedEventArgs e) - { - _parent.GoBack(); - } - - private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) - { - if (ViewModel.FirmwareFound) - { - _parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile)); - } - } - - private static byte[] ProcessProfileImage(byte[] buffer) - { - using Image image = Image.Load(buffer); - - image.Mutate(x => x.Resize(256, 256)); - - using MemoryStream streamJpg = new(); - - image.SaveAsJpeg(streamJpg); - - return streamJpg.ToArray(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml deleted file mode 100644 index 3fdb4ab9..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml +++ /dev/null @@ -1,82 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - d:DesignWidth="550" - d:DesignHeight="450" - Width="500" - Height="400" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView" - x:DataType="viewModels:UserProfileViewModel" - Focusable="True"> - <Design.DataContext> - <viewModels:UserProfileViewModel /> - </Design.DataContext> - <Grid HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition/> - <RowDefinition Height="Auto"/> - </Grid.RowDefinitions> - <Border - CornerRadius="5" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1" - Grid.Row="0"> - <Panel> - <ListBox - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ItemsSource="{Binding LostProfiles}"> - <ListBox.ItemTemplate> - <DataTemplate> - <Border - Margin="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ClipToBounds="True" - CornerRadius="5"> - <Grid Margin="0"> - <Grid.ColumnDefinitions> - <ColumnDefinition/> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> - <TextBlock - HorizontalAlignment="Stretch" - Text="{Binding UserId}" - TextAlignment="Start" - TextWrapping="Wrap" /> - <Button Grid.Column="1" - HorizontalAlignment="Right" - Click="Recover" - CommandParameter="{Binding}" - Content="{locale:Locale Recover}"/> - </Grid> - </Border> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - <TextBlock - IsVisible="{Binding IsEmpty}" - TextAlignment="Center" - Text="{locale:Locale UserProfilesRecoverEmptyList}"/> - </Panel> - </Border> - <StackPanel - Grid.Row="1" - Margin="0 24 0 0" - Orientation="Horizontal"> - <Button - Width="50" - MinWidth="50" - Click="GoBack"> - <ui:SymbolIcon Symbol="Back"/> - </Button> - </StackPanel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs deleted file mode 100644 index 31934349..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserRecovererView : UserControl - { - private NavigationDialogHost _parent; - - public UserRecovererView() - { - InitializeComponent(); - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - switch (arg.NavigationMode) - { - case NavigationMode.New: - var parent = (NavigationDialogHost)arg.Parameter; - - _parent = parent; - - ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}"; - - break; - } - } - } - - private void GoBack(object sender, RoutedEventArgs e) - { - _parent?.GoBack(); - } - - private void Recover(object sender, RoutedEventArgs e) - { - _parent?.RecoverLostAccounts(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml deleted file mode 100644 index 8bc5125a..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml +++ /dev/null @@ -1,213 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - mc:Ignorable="d" - d:DesignWidth="600" - d:DesignHeight="500" - Height="450" - Width="550" - x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView" - x:DataType="viewModels:UserSaveManagerViewModel" - Focusable="True"> - <Design.DataContext> - <viewModels:UserSaveManagerViewModel /> - </Design.DataContext> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - </UserControl.Resources> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid - Grid.Row="0" - HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <StackPanel - Spacing="10" - Orientation="Horizontal" - HorizontalAlignment="Left" - VerticalAlignment="Center"> - <Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" /> - <ComboBox SelectedIndex="{Binding SortIndex}" Width="100"> - <ComboBoxItem> - <Label - VerticalAlignment="Center" - HorizontalContentAlignment="Left" - Content="{locale:Locale Name}" /> - </ComboBoxItem> - <ComboBoxItem> - <Label - VerticalAlignment="Center" - HorizontalContentAlignment="Left" - Content="{locale:Locale Size}" /> - </ComboBoxItem> - <ComboBox.Styles> - <Style Selector="ContentControl#ContentPresenter"> - <Setter Property="HorizontalAlignment" Value="Left" /> - </Style> - </ComboBox.Styles> - </ComboBox> - <ComboBox SelectedIndex="{Binding OrderIndex}" Width="150"> - <ComboBoxItem> - <Label - VerticalAlignment="Center" - HorizontalContentAlignment="Left" - Content="{locale:Locale OrderAscending}" /> - </ComboBoxItem> - <ComboBoxItem> - <Label - VerticalAlignment="Center" - HorizontalContentAlignment="Left" - Content="{locale:Locale OrderDescending}" /> - </ComboBoxItem> - <ComboBox.Styles> - <Style Selector="ContentControl#ContentPresenter"> - <Setter Property="HorizontalAlignment" Value="Left" /> - </Style> - </ComboBox.Styles> - </ComboBox> - </StackPanel> - <Grid - Grid.Column="1" - HorizontalAlignment="Stretch" - Margin="10,0, 0, 0"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto"/> - <ColumnDefinition/> - </Grid.ColumnDefinitions> - <Label Content="{locale:Locale Search}" VerticalAlignment="Center" /> - <TextBox - Margin="5,0,0,0" - Grid.Column="1" - HorizontalAlignment="Stretch" - Text="{Binding Search}" /> - </Grid> - </Grid> - <Border - Grid.Row="1" - Margin="0,5" - BorderThickness="1" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - CornerRadius="5" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <ListBox - Name="SaveList" - ItemsSource="{Binding Views}" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Padding" Value="10" /> - <Setter Property="Margin" Value="5" /> - <Setter Property="CornerRadius" Value="4" /> - </Style> - <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> - <Setter Property="IsVisible" Value="False" /> - </Style> - </ListBox.Styles> - <ListBox.ItemTemplate> - <DataTemplate x:DataType="models:SaveModel"> - <Grid HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <StackPanel - Grid.Column="0" - Orientation="Horizontal" - Spacing="5"> - <Border - Height="42" - Width="42" - Padding="10" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1" - IsVisible="{Binding !InGameList}"> - <ui:SymbolIcon - Symbol="Help" - FontSize="30" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Border> - <Image - IsVisible="{Binding InGameList}" - Width="42" - Height="42" - Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> - <TextBlock - MaxLines="3" - Width="320" - Margin="5" - TextWrapping="Wrap" - Text="{Binding Title}" - VerticalAlignment="Center" /> - </StackPanel> - <StackPanel - Grid.Column="1" - Spacing="10" - HorizontalAlignment="Right" - Orientation="Horizontal"> - <Label - Content="{Binding SizeString}" - IsVisible="{Binding SizeAvailable}" - VerticalAlignment="Center" - HorizontalAlignment="Right" /> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Name="OpenLocation" - Click="OpenLocation"> - <ui:SymbolIcon - Symbol="OpenFolder" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Name="Delete" - Click="Delete"> - <ui:SymbolIcon - Symbol="Delete" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - </StackPanel> - </Grid> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - </Border> - <StackPanel - Grid.Row="2" - Margin="0 24 0 0" - Orientation="Horizontal"> - <Button - Width="50" - MinWidth="50" - Click="GoBack"> - <ui:SymbolIcon Symbol="Back" /> - </Button> - </StackPanel> - </Grid> -</UserControl> \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs deleted file mode 100644 index 00a229fa..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using LibHac; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Shim; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System; -using System.Collections.ObjectModel; -using System.Threading.Tasks; -using Button = Avalonia.Controls.Button; -using UserId = LibHac.Fs.UserId; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserSaveManagerView : UserControl - { - internal UserSaveManagerViewModel ViewModel { get; private set; } - - private AccountManager _accountManager; - private HorizonClient _horizonClient; - private VirtualFileSystem _virtualFileSystem; - private NavigationDialogHost _parent; - - public UserSaveManagerView() - { - InitializeComponent(); - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - switch (arg.NavigationMode) - { - case NavigationMode.New: - var (parent, accountManager, client, virtualFileSystem) = ((NavigationDialogHost parent, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter; - _accountManager = accountManager; - _horizonClient = client; - _virtualFileSystem = virtualFileSystem; - - _parent = parent; - break; - } - - DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager); - ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}"; - - Task.Run(LoadSaves); - } - } - - public void LoadSaves() - { - ViewModel.Saves.Clear(); - var saves = new ObservableCollection<SaveModel>(); - var saveDataFilter = SaveDataFilter.Make( - programId: default, - saveType: SaveDataType.Account, - new UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low), - saveDataId: default, - index: default); - - using var saveDataIterator = new UniqueRef<SaveDataIterator>(); - - _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); - - Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10]; - - while (true) - { - saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); - - if (readCount == 0) - { - break; - } - - for (int i = 0; i < readCount; i++) - { - var save = saveDataInfo[i]; - if (save.ProgramId.Value != 0) - { - var saveModel = new SaveModel(save); - saves.Add(saveModel); - } - } - } - - Dispatcher.UIThread.Post(() => - { - ViewModel.Saves = saves; - ViewModel.Sort(); - }); - } - - private void GoBack(object sender, RoutedEventArgs e) - { - _parent?.GoBack(); - } - - private void OpenLocation(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is SaveModel saveModel) - { - ApplicationHelper.OpenSaveDir(saveModel.SaveId); - } - } - } - - private async void Delete(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is SaveModel saveModel) - { - var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave], - LocaleManager.Instance[LocaleKeys.IrreversibleActionNote], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], ""); - - if (result == UserResult.Yes) - { - _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId); - ViewModel.Saves.Remove(saveModel); - ViewModel.Sort(); - } - } - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml deleted file mode 100644 index 3a9de303..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml +++ /dev/null @@ -1,162 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Views.User.UserSelectorViews" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - d:DesignHeight="450" - MinWidth="500" - d:DesignWidth="800" - mc:Ignorable="d" - Focusable="True" - x:DataType="viewModels:UserProfileViewModel"> - <UserControl.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - </UserControl.Resources> - <Design.DataContext> - <viewModels:UserProfileViewModel /> - </Design.DataContext> - <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Border - CornerRadius="5" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1"> - <ListBox - MaxHeight="300" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - SelectionChanged="ProfilesList_SelectionChanged" - Background="Transparent" - ItemsSource="{Binding Profiles}"> - <ListBox.ItemsPanel> - <ItemsPanelTemplate> - <WrapPanel - HorizontalAlignment="Left" - VerticalAlignment="Center" - Orientation="Horizontal"/> - </ItemsPanelTemplate> - </ListBox.ItemsPanel> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Margin" Value="5 5 0 5" /> - <Setter Property="CornerRadius" Value="5" /> - </Style> - <Style Selector="Rectangle#SelectionIndicator"> - <Setter Property="Opacity" Value="0" /> - </Style> - </ListBox.Styles> - <ListBox.DataTemplates> - <DataTemplate - DataType="models:UserProfile"> - <Grid - PointerEntered="Grid_PointerEntered" - PointerExited="Grid_OnPointerExited"> - <Border - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ClipToBounds="True" - CornerRadius="5" - Background="{Binding BackgroundColor}"> - <StackPanel - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Image - Width="96" - Height="96" - HorizontalAlignment="Stretch" - VerticalAlignment="Top" - Source="{Binding Image, Converter={StaticResource ByteImage}}" /> - <TextBlock - HorizontalAlignment="Stretch" - MaxWidth="90" - Text="{Binding Name}" - TextAlignment="Center" - TextWrapping="Wrap" - TextTrimming="CharacterEllipsis" - MaxLines="2" - Margin="5" /> - </StackPanel> - </Border> - <Border - Margin="2" - Height="24" - Width="24" - CornerRadius="12" - HorizontalAlignment="Right" - VerticalAlignment="Top" - Background="{DynamicResource ThemeContentBackgroundColor}" - IsVisible="{Binding IsPointerOver}"> - <Button - MaxHeight="24" - MaxWidth="24" - MinHeight="24" - MinWidth="24" - CornerRadius="12" - Padding="0" - Click="EditUser"> - <ui:SymbolIcon Symbol="Edit" /> - </Button> - </Border> - </Grid> - </DataTemplate> - <DataTemplate - DataType="viewModels:BaseModel"> - <Panel - Height="118" - Width="96"> - <Button - MinWidth="50" - MinHeight="50" - MaxWidth="50" - MaxHeight="50" - CornerRadius="25" - Margin="10" - Padding="0" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Click="AddUser"> - <ui:SymbolIcon Symbol="Add" /> - </Button> - <Panel.Styles> - <Style Selector="Panel"> - <Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/> - </Style> - </Panel.Styles> - </Panel> - </DataTemplate> - </ListBox.DataTemplates> - </ListBox> - </Border> - <StackPanel - Grid.Row="1" - Margin="0 24 0 0" - HorizontalAlignment="Left" - Orientation="Horizontal" - Spacing="10"> - <Button - Click="ManageSaves" - Content="{locale:Locale UserProfilesManageSaves}" /> - <Button - Click="RecoverLostAccounts" - Content="{locale:Locale UserProfilesRecoverLostAccounts}" /> - </StackPanel> - <StackPanel - Grid.Row="1" - Margin="0 24 0 0" - HorizontalAlignment="Right" - Orientation="Horizontal"> - <Button - Click="Close" - Content="{locale:Locale UserProfilesClose}" /> - </StackPanel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs deleted file mode 100644 index fa3383aa..00000000 --- a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Navigation; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Button = Avalonia.Controls.Button; - -namespace Ryujinx.Ava.UI.Views.User -{ - public partial class UserSelectorViews : UserControl - { - private NavigationDialogHost _parent; - - public UserProfileViewModel ViewModel { get; set; } - - public UserSelectorViews() - { - InitializeComponent(); - - if (Program.PreviewerDetached) - { - AddHandler(Frame.NavigatedToEvent, (s, e) => - { - NavigatedTo(e); - }, RoutingStrategies.Direct); - } - } - - private void NavigatedTo(NavigationEventArgs arg) - { - if (Program.PreviewerDetached) - { - if (arg.NavigationMode == NavigationMode.New) - { - _parent = (NavigationDialogHost)arg.Parameter; - ViewModel = _parent.ViewModel; - } - - if (arg.NavigationMode == NavigationMode.Back) - { - ((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]; - } - - DataContext = ViewModel; - } - } - - private void Grid_PointerEntered(object sender, PointerEventArgs e) - { - if (sender is Grid grid) - { - if (grid.DataContext is UserProfile profile) - { - profile.IsPointerOver = true; - } - } - } - - private void Grid_OnPointerExited(object sender, PointerEventArgs e) - { - if (sender is Grid grid) - { - if (grid.DataContext is UserProfile profile) - { - profile.IsPointerOver = false; - } - } - } - - private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is ListBox listBox) - { - int selectedIndex = listBox.SelectedIndex; - - if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count) - { - if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile) - { - _parent?.AccountManager?.OpenUser(userProfile.UserId); - - foreach (BaseModel profile in ViewModel.Profiles) - { - if (profile is UserProfile uProfile) - { - uProfile.UpdateState(); - } - } - } - } - } - } - - private void AddUser(object sender, RoutedEventArgs e) - { - _parent.AddUser(); - } - - private void EditUser(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is UserProfile userProfile) - { - _parent.EditUser(userProfile); - } - } - } - - private void ManageSaves(object sender, RoutedEventArgs e) - { - _parent.ManageSaves(); - } - - private void RecoverLostAccounts(object sender, RoutedEventArgs e) - { - _parent.RecoverLostAccounts(); - } - - private void Close(object sender, RoutedEventArgs e) - { - ((ContentDialog)_parent.Parent).Hide(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml deleted file mode 100644 index 69fa8251..00000000 --- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml +++ /dev/null @@ -1,270 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Windows.AboutWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels" - Width="550" - Height="260" - Margin="0,-12,0,0" - d:DesignHeight="260" - d:DesignWidth="550" - x:DataType="viewModel:AboutWindowViewModel" - Focusable="True" - mc:Ignorable="d"> - <Design.DataContext> - <viewModel:AboutWindowViewModel /> - </Design.DataContext> - <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <Grid - Grid.Column="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <StackPanel - Grid.Row="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Spacing="10"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <StackPanel - Grid.Column="1" - Orientation="Horizontal" - HorizontalAlignment="Center" - Spacing="10"> - <Image - Height="80" - Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" - HorizontalAlignment="Center" - IsHitTestVisible="True" /> - <WrapPanel - HorizontalAlignment="Right" - VerticalAlignment="Center" - Orientation="Vertical"> - <TextBlock - FontSize="28" - FontWeight="Bold" - Text="Ryujinx" - TextAlignment="Start" - Width="110" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - <TextBlock - FontSize="11" - Text="(REE-YOU-JINX)" - TextAlignment="Start" - Width="110" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </WrapPanel> - </StackPanel> - </Grid> - <TextBlock - HorizontalAlignment="Center" - VerticalAlignment="Center" - FontSize="10" - LineHeight="12" - Text="{Binding Version}" - TextAlignment="Center" /> - <Button - Padding="5" - HorizontalAlignment="Center" - Background="Transparent" - Click="Button_OnClick" - Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"> - <TextBlock - FontSize="10" - Text="{locale:Locale AboutChangelogButton}" - TextAlignment="Center" - ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" /> - </Button> - </StackPanel> - <StackPanel - Grid.Row="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Spacing="10"> - <TextBlock - Width="200" - HorizontalAlignment="Center" - FontSize="10" - LineHeight="12" - Text="{locale:Locale AboutDisclaimerMessage}" - TextAlignment="Center" - TextWrapping="Wrap" /> - <TextBlock - Name="AmiiboLabel" - Width="200" - HorizontalAlignment="Center" - FontSize="10" - LineHeight="12" - PointerPressed="AmiiboLabel_OnPointerPressed" - Text="{locale:Locale AboutAmiiboDisclaimerMessage}" - TextAlignment="Center" - TextWrapping="Wrap" /> - <StackPanel - HorizontalAlignment="Center" - Orientation="Horizontal" - Spacing="10"> - <Button - MinWidth="30" - MinHeight="30" - MaxWidth="30" - MaxHeight="30" - Padding="8" - Background="Transparent" - Click="Button_OnClick" - CornerRadius="15" - Tag="https://www.patreon.com/ryujinx" - ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> - <Image Source="{Binding PatreonLogo}" /> - </Button> - <Button - MinWidth="30" - MinHeight="30" - MaxWidth="30" - MaxHeight="30" - Padding="8" - Background="Transparent" - Click="Button_OnClick" - CornerRadius="15" - Tag="https://github.com/Ryujinx/Ryujinx" - ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> - <Image Source="{Binding GithubLogo}" /> - </Button> - <Button - MinWidth="30" - MinHeight="30" - MaxWidth="30" - MaxHeight="30" - Padding="8" - Background="Transparent" - Click="Button_OnClick" - CornerRadius="15" - Tag="https://discordapp.com/invite/N2FmfVc" - ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> - <Image Source="{Binding DiscordLogo}" /> - </Button> - <Button - MinWidth="30" - MinHeight="30" - MaxWidth="30" - MaxHeight="30" - Padding="8" - Background="Transparent" - Click="Button_OnClick" - CornerRadius="15" - Tag="https://twitter.com/RyujinxEmu" - ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> - <Image Source="{Binding TwitterLogo}" /> - </Button> - <Button - MinWidth="30" - MinHeight="30" - MaxWidth="30" - MaxHeight="30" - Padding="8" - Background="Transparent" - Click="Button_OnClick" - CornerRadius="15" - Tag="https://www.ryujinx.org" - ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}"> - <ui:SymbolIcon Foreground="{DynamicResource ThemeForegroundColor}" Symbol="Link" /> - </Button> - </StackPanel> - </StackPanel> - </Grid> - <Border - Grid.Column="1" - Width="1" - Margin="20,0" - VerticalAlignment="Stretch" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="1,0,0,0" /> - <Grid - Grid.Column="2" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <StackPanel - Grid.Row="0" - Margin="0,10,0,0" - Spacing="2"> - <TextBlock - FontSize="15" - FontWeight="Bold" - Text="{locale:Locale AboutRyujinxAboutTitle}" /> - <TextBlock - FontSize="10" - Text="{locale:Locale AboutRyujinxAboutContent}" - TextWrapping="Wrap" /> - </StackPanel> - <StackPanel - Grid.Row="1" - Margin="0,10,0,0" - Spacing="2"> - <TextBlock - FontSize="15" - FontWeight="Bold" - Text="{locale:Locale AboutRyujinxMaintainersTitle}" /> - <TextBlock - FontSize="10" - Text="{Binding Developers}" - TextWrapping="Wrap" /> - <Button - Padding="5" - HorizontalAlignment="Left" - Background="Transparent" - Click="Button_OnClick" - Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"> - <TextBlock - FontSize="10" - Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" - TextAlignment="End" - ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" /> - </Button> - </StackPanel> - <StackPanel - Grid.Row="2" - Margin="0,10,0,0" - Spacing="2"> - <TextBlock - FontSize="15" - FontWeight="Bold" - Text="{locale:Locale AboutRyujinxSupprtersTitle}" /> - <ScrollViewer - Height="70" - HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Visible"> - <TextBlock - Name="SupportersTextBlock" - VerticalAlignment="Top" - FontSize="10" - Text="{Binding Supporters}" - TextWrapping="Wrap" /> - </ScrollViewer> - </StackPanel> - </Grid> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs deleted file mode 100644 index c32661b0..00000000 --- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Layout; -using Avalonia.Styling; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.UI.Common.Helper; -using System.Threading.Tasks; -using Button = Avalonia.Controls.Button; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class AboutWindow : UserControl - { - public AboutWindow() - { - DataContext = new AboutWindowViewModel(); - - InitializeComponent(); - } - - public static async Task Show() - { - ContentDialog contentDialog = new() - { - PrimaryButtonText = "", - SecondaryButtonText = "", - CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], - Content = new AboutWindow(), - }; - - Style closeButton = new(x => x.Name("CloseButton")); - closeButton.Setters.Add(new Setter(WidthProperty, 80d)); - - Style closeButtonParent = new(x => x.Name("CommandSpace")); - closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Right)); - - contentDialog.Styles.Add(closeButton); - contentDialog.Styles.Add(closeButtonParent); - - await ContentDialogHelper.ShowAsync(contentDialog); - } - - private void Button_OnClick(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - OpenHelper.OpenUrl(button.Tag.ToString()); - } - } - - private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e) - { - if (sender is TextBlock) - { - OpenHelper.OpenUrl("https://amiiboapi.com"); - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml deleted file mode 100644 index c587aa87..00000000 --- a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml +++ /dev/null @@ -1,75 +0,0 @@ -<window:StyleableWindow - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - mc:Ignorable="d" - d:DesignWidth="400" - d:DesignHeight="350" - x:Class="Ryujinx.Ava.UI.Windows.AmiiboWindow" - x:DataType="viewModels:AmiiboWindowViewModel" - CanResize="False" - WindowStartupLocation="CenterOwner" - Width="800" - MinHeight="650" - Height="650" - SizeToContent="Manual" - MinWidth="600" - Focusable="True"> - <Design.DataContext> - <viewModels:AmiiboWindowViewModel /> - </Design.DataContext> - <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid Grid.Row="1" HorizontalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition /> - <ColumnDefinition /> - </Grid.ColumnDefinitions> - <StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboSeriesLabel}" /> - <ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" /> - </StackPanel> - <StackPanel Spacing="10" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right"> - <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboCharacterLabel}" /> - <ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" ItemsSource="{Binding AmiiboList}" /> - </StackPanel> - </Grid> - <StackPanel Margin="20" Grid.Row="2"> - <Image Source="{Binding AmiiboImage}" Height="350" Width="350" HorizontalAlignment="Center" /> - <ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" - Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch"> - <TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center" - TextAlignment="Center" /> - </ScrollViewer> - </StackPanel> - <Grid Grid.Row="3"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding ShowAllAmiibo}" - Content="{locale:Locale AmiiboOptionsShowAllLabel}" /> - <CheckBox Margin="10" VerticalContentAlignment="Center" Grid.Column="1" IsChecked="{Binding UseRandomUuid}" - Content="{locale:Locale AmiiboOptionsUsRandomTagLabel}" /> - - <Button Grid.Column="3" IsEnabled="{Binding EnableScanning}" Width="80" - Content="{locale:Locale AmiiboScanButtonLabel}" Name="ScanButton" - Click="ScanButton_Click" /> - <Button Grid.Column="4" Margin="10,0" Width="80" Content="{locale:Locale InputDialogCancel}" - Name="CancelButton" - Click="CancelButton_Click" /> - </Grid> - </Grid> -</window:StyleableWindow> diff --git a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs deleted file mode 100644 index 8829cb10..00000000 --- a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Avalonia.Interactivity; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.UI.Common.Models.Amiibo; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class AmiiboWindow : StyleableWindow - { - public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId) - { - ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId) - { - ShowAllAmiibo = showAll, - }; - - DataContext = ViewModel; - - InitializeComponent(); - - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo]; - } - - public AmiiboWindow() - { - ViewModel = new AmiiboWindowViewModel(this, string.Empty, string.Empty); - - DataContext = ViewModel; - - InitializeComponent(); - - if (Program.PreviewerDetached) - { - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo]; - } - } - - public bool IsScanned { get; set; } - public AmiiboApi ScannedAmiibo { get; set; } - public AmiiboWindowViewModel ViewModel { get; set; } - - private void ScanButton_Click(object sender, RoutedEventArgs e) - { - if (ViewModel.AmiiboSelectedIndex > -1) - { - AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; - ScannedAmiibo = amiibo; - IsScanned = true; - Close(); - } - } - - private void CancelButton_Click(object sender, RoutedEventArgs e) - { - IsScanned = false; - - Close(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml deleted file mode 100644 index 57d5f7ef..00000000 --- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml +++ /dev/null @@ -1,126 +0,0 @@ -<window:StyleableWindow - x:Class="Ryujinx.Ava.UI.Windows.CheatWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:model="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" - Width="500" - Height="500" - MinWidth="500" - MinHeight="500" - x:DataType="window:CheatWindow" - WindowStartupLocation="CenterOwner" - mc:Ignorable="d" - Focusable="True"> - <Window.Styles> - <Style Selector="TreeViewItem"> - <Setter Property="IsExpanded" Value="True" /> - </Style> - </Window.Styles> - <Grid Name="CheatGrid" Margin="15"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <TextBlock - Grid.Row="1" - Grid.Column="0" - Grid.ColumnSpan="2" - MaxWidth="500" - Margin="20,15,20,5" - HorizontalAlignment="Center" - VerticalAlignment="Center" - LineHeight="18" - Text="{Binding Heading}" - TextAlignment="Center" - TextWrapping="Wrap" /> - <TextBlock - Grid.Row="2" - Grid.Column="0" - MaxWidth="500" - Margin="140,15,20,5" - HorizontalAlignment="Center" - VerticalAlignment="Center" - LineHeight="30" - Text="{locale:Locale BuildId}" - TextAlignment="Center" - TextWrapping="Wrap" /> - <TextBox - Grid.Row="2" - Grid.Column="1" - Margin="0,5,110,5" - MinWidth="160" - HorizontalAlignment="Center" - VerticalAlignment="Center" - Text="{Binding BuildId}" - IsReadOnly="True" /> - <Border - Grid.Row="3" - Grid.Column="0" - Grid.ColumnSpan="2" - Margin="5" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - BorderBrush="Gray" - BorderThickness="1"> - <TreeView - Name="CheatsView" - MinHeight="300" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - ItemsSource="{Binding LoadedCheats}"> - <TreeView.Styles> - <Styles> - <Style Selector="TreeViewItem:empty /template/ ItemsPresenter"> - <Setter Property="IsVisible" Value="False" /> - </Style> - </Styles> - </TreeView.Styles> - <TreeView.ItemTemplate> - <TreeDataTemplate ItemsSource="{Binding SubNodes}"> - <StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> - <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" /> - <TextBlock Width="150" Text="{Binding CleanName}" IsVisible="{Binding !IsRootNode}" /> - <TextBlock Width="150" Text="{Binding BuildId}" IsVisible="{Binding IsRootNode}" /> - <TextBlock Text="{Binding Path}" IsVisible="{Binding IsRootNode}" /> - </StackPanel> - </TreeDataTemplate> - </TreeView.ItemTemplate> - </TreeView> - </Border> - <DockPanel - Grid.Row="4" - Grid.Column="0" - Grid.ColumnSpan="2" - Margin="0" - HorizontalAlignment="Stretch"> - <DockPanel Margin="0" HorizontalAlignment="Right"> - <Button - Name="SaveButton" - MinWidth="90" - Margin="5" - Command="{Binding Save}" - IsVisible="{Binding !NoCheatsFound}"> - <TextBlock Text="{locale:Locale SettingsButtonSave}" /> - </Button> - <Button - Name="CancelButton" - MinWidth="90" - Margin="5" - Command="{Binding Close}"> - <TextBlock Text="{locale:Locale InputDialogCancel}" /> - </Button> - </DockPanel> - </DockPanel> - </Grid> -</window:StyleableWindow> diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs deleted file mode 100644 index d78e48a4..00000000 --- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Avalonia.Collections; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.UI.App.Common; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class CheatWindow : StyleableWindow - { - private readonly string _enabledCheatsPath; - public bool NoCheatsFound { get; } - - public AvaloniaList<CheatNode> LoadedCheats { get; } - - public string Heading { get; } - public string BuildId { get; } - - public CheatWindow() - { - DataContext = this; - - InitializeComponent(); - - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; - } - - public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath) - { - LoadedCheats = new AvaloniaList<CheatNode>(); - - Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper()); - BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath); - - InitializeComponent(); - - string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId); - ulong titleIdValue = ulong.Parse(titleId, NumberStyles.HexNumber); - - _enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt"); - - string[] enabled = Array.Empty<string>(); - - if (File.Exists(_enabledCheatsPath)) - { - enabled = File.ReadAllLines(_enabledCheatsPath); - } - - int cheatAdded = 0; - - var mods = new ModLoader.ModCache(); - - ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue); - - string currentCheatFile = string.Empty; - string buildId = string.Empty; - - CheatNode currentGroup = null; - - foreach (var cheat in mods.Cheats) - { - if (cheat.Path.FullName != currentCheatFile) - { - currentCheatFile = cheat.Path.FullName; - string parentPath = currentCheatFile.Replace(titleModsPath, ""); - - buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); - currentGroup = new CheatNode("", buildId, parentPath, true); - - LoadedCheats.Add(currentGroup); - } - - var model = new CheatNode(cheat.Name, buildId, "", false, enabled.Contains($"{buildId}-{cheat.Name}")); - currentGroup?.SubNodes.Add(model); - - cheatAdded++; - } - - if (cheatAdded == 0) - { - NoCheatsFound = true; - } - - DataContext = this; - - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; - } - - public void Save() - { - if (NoCheatsFound) - { - return; - } - - List<string> enabledCheats = new(); - - foreach (var cheats in LoadedCheats) - { - foreach (var cheat in cheats.SubNodes) - { - if (cheat.IsEnabled) - { - enabledCheats.Add(cheat.BuildIdKey); - } - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)); - - File.WriteAllLines(_enabledCheatsPath, enabledCheats); - - Close(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml b/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml deleted file mode 100644 index 8b52bade..00000000 --- a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml +++ /dev/null @@ -1,25 +0,0 @@ -<window:StyleableWindow - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" - mc:Ignorable="d" - d:DesignWidth="800" - d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow" - Title="ContentDialogOverlayWindow" - Focusable="False"> - <window:StyleableWindow.Styles> - <Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot"> - <Setter Property="Background" - Value="Transparent" /> - </Style> - </window:StyleableWindow.Styles> - <ui:ContentDialog Name="ContentDialog" - IsPrimaryButtonEnabled="True" - IsSecondaryButtonEnabled="True" - IsVisible="False" - Focusable="True"/> -</window:StyleableWindow> diff --git a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs deleted file mode 100644 index 2b12d72f..00000000 --- a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Media; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class ContentDialogOverlayWindow : StyleableWindow - { - public ContentDialogOverlayWindow() - { - InitializeComponent(); - - ExtendClientAreaToDecorationsHint = true; - TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; - WindowStartupLocation = WindowStartupLocation.Manual; - SystemDecorations = SystemDecorations.None; - ExtendClientAreaTitleBarHeightHint = 0; - Background = Brushes.Transparent; - CanResize = false; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml deleted file mode 100644 index 99cf28e7..00000000 --- a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml +++ /dev/null @@ -1,192 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - Width="500" - Height="380" - mc:Ignorable="d" - x:DataType="viewModels:DownloadableContentManagerViewModel" - Focusable="True"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Panel - Margin="0 0 0 10" - Grid.Row="0"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <TextBlock - Grid.Column="0" - Text="{Binding UpdateCount}" /> - <StackPanel - Margin="10 0" - Grid.Column="1" - Orientation="Horizontal"> - <Button - Name="EnableAllButton" - MinWidth="90" - Margin="5" - Command="{Binding EnableAll}"> - <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> - </Button> - <Button - Name="DisableAllButton" - MinWidth="90" - Margin="5" - Command="{Binding DisableAll}"> - <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> - </Button> - </StackPanel> - <TextBox - Grid.Column="2" - MinHeight="29" - MaxHeight="29" - HorizontalAlignment="Stretch" - Watermark="{locale:Locale Search}" - Text="{Binding Search}" /> - </Grid> - </Panel> - <Border - Grid.Row="1" - Margin="0 0 0 24" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1" - CornerRadius="5" - Padding="2.5"> - <ListBox - AutoScrollToSelectedItem="False" - SelectionMode="Multiple, Toggle" - Background="Transparent" - SelectionChanged="OnSelectionChanged" - SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}" - ItemsSource="{Binding Views}"> - <ListBox.DataTemplates> - <DataTemplate - DataType="models:DownloadableContentModel"> - <Panel Margin="10"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <Grid - Grid.Column="0"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <TextBlock - Grid.Column="0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - MaxLines="2" - TextWrapping="Wrap" - TextTrimming="CharacterEllipsis" - Text="{Binding FileName}" /> - <TextBlock - Grid.Column="1" - Margin="10 0" - HorizontalAlignment="Left" - VerticalAlignment="Center" - Text="{Binding TitleId}" /> - </Grid> - <StackPanel - Grid.Column="1" - Spacing="10" - Orientation="Horizontal" - HorizontalAlignment="Right"> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="OpenLocation"> - <ui:SymbolIcon - Symbol="OpenFolder" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="RemoveDLC"> - <ui:SymbolIcon - Symbol="Cancel" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - </StackPanel> - </Grid> - </Panel> - </DataTemplate> - </ListBox.DataTemplates> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Background" Value="Transparent" /> - </Style> - </ListBox.Styles> - </ListBox> - </Border> - <Panel - Grid.Row="2" - HorizontalAlignment="Stretch"> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Left"> - <Button - Name="AddButton" - MinWidth="90" - Margin="5" - Command="{Binding Add}"> - <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> - </Button> - <Button - Name="RemoveAllButton" - MinWidth="90" - Margin="5" - Command="{Binding RemoveAll}"> - <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> - </Button> - </StackPanel> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Right"> - <Button - Name="SaveButton" - MinWidth="90" - Margin="5" - Click="SaveAndClose"> - <TextBlock Text="{locale:Locale SettingsButtonSave}" /> - </Button> - <Button - Name="CancelButton" - MinWidth="90" - Margin="5" - Click="Close"> - <TextBlock Text="{locale:Locale InputDialogCancel}" /> - </Button> - </StackPanel> - </Panel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs deleted file mode 100644 index 0c02fa0f..00000000 --- a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Styling; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -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 System.Threading.Tasks; -using Button = Avalonia.Controls.Button; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class DownloadableContentManagerWindow : UserControl - { - public DownloadableContentManagerViewModel ViewModel; - - public DownloadableContentManagerWindow() - { - DataContext = this; - - InitializeComponent(); - } - - public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId) - { - DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId); - - InitializeComponent(); - } - - public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) - { - ContentDialog contentDialog = new() - { - PrimaryButtonText = "", - SecondaryButtonText = "", - CloseButtonText = "", - Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId), - Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")), - }; - - Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); - bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); - - contentDialog.Styles.Add(bottomBorder); - - await ContentDialogHelper.ShowAsync(contentDialog); - } - - private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs) - { - ViewModel.Save(); - ((ContentDialog)Parent).Hide(); - } - - private void Close(object sender, RoutedEventArgs e) - { - ((ContentDialog)Parent).Hide(); - } - - private void RemoveDLC(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is DownloadableContentModel model) - { - ViewModel.Remove(model); - } - } - } - - private void OpenLocation(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is DownloadableContentModel model) - { - OpenHelper.LocateFile(model.ContainerPath); - } - } - } - - private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - foreach (var content in e.AddedItems) - { - if (content is DownloadableContentModel model) - { - var index = ViewModel.DownloadableContents.IndexOf(model); - - if (index != -1) - { - ViewModel.DownloadableContents[index].Enabled = true; - } - } - } - - foreach (var content in e.RemovedItems) - { - if (content is DownloadableContentModel model) - { - var index = ViewModel.DownloadableContents.IndexOf(model); - - if (index != -1) - { - ViewModel.DownloadableContents[index].Enabled = false; - } - } - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs b/src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs deleted file mode 100644 index 4c75a5ff..00000000 --- a/src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs +++ /dev/null @@ -1,194 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Ava.UI.Windows -{ - static class IconColorPicker - { - private const int ColorsPerLine = 64; - private const int TotalColors = ColorsPerLine * ColorsPerLine; - - private const int UvQuantBits = 3; - private const int UvQuantShift = BitsPerComponent - UvQuantBits; - - private const int SatQuantBits = 5; - private const int SatQuantShift = BitsPerComponent - SatQuantBits; - - private const int BitsPerComponent = 8; - - private const int CutOffLuminosity = 64; - - private readonly struct PaletteColor - { - public int Qck { get; } - public byte R { get; } - public byte G { get; } - public byte B { get; } - - public PaletteColor(int qck, byte r, byte g, byte b) - { - Qck = qck; - R = r; - G = g; - B = b; - } - } - - public static Color GetFilteredColor(Image<Bgra32> image) - { - var color = GetColor(image).ToPixel<Bgra32>(); - - // We don't want colors that are too dark. - // If the color is too dark, make it brighter by reducing the range - // and adding a constant color. - int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B); - if (luminosity < CutOffLuminosity) - { - color = Color.FromRgb( - (byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue), - (byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue), - (byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue)); - } - - return color; - } - - public static Color GetColor(Image<Bgra32> image) - { - var colors = new PaletteColor[TotalColors]; - - var dominantColorBin = new Dictionary<int, int>(); - - var buffer = GetBuffer(image); - - int w = image.Width; - - int w8 = w << 8; - int h8 = image.Height << 8; - -#pragma warning disable IDE0059 // Unnecessary assignment - int xStep = w8 / ColorsPerLine; - int yStep = h8 / ColorsPerLine; -#pragma warning restore IDE0059 - - int i = 0; - int maxHitCount = 0; - - for (int y = 0; y < image.Height; y++) - { - int yOffset = y * image.Width; - - for (int x = 0; x < image.Width && i < TotalColors; x++) - { - int offset = x + yOffset; - - byte cb = buffer[offset].B; - byte cg = buffer[offset].G; - byte cr = buffer[offset].R; - - var qck = GetQuantizedColorKey(cr, cg, cb); - - if (dominantColorBin.TryGetValue(qck, out int hitCount)) - { - dominantColorBin[qck] = hitCount + 1; - - if (maxHitCount < hitCount) - { - maxHitCount = hitCount; - } - } - else - { - dominantColorBin.Add(qck, 1); - } - - colors[i++] = new PaletteColor(qck, cr, cg, cb); - } - } - - int highScore = -1; - PaletteColor bestCandidate = default; - - for (i = 0; i < TotalColors; i++) - { - var score = GetColorScore(dominantColorBin, maxHitCount, colors[i]); - - if (highScore < score) - { - highScore = score; - bestCandidate = colors[i]; - } - } - - return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B); - } - - public static Bgra32[] GetBuffer(Image<Bgra32> image) - { - return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty<Bgra32>(); - } - - private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color) - { - var hitCount = dominantColorBin[color.Qck]; - var balancedHitCount = BalanceHitCount(hitCount, maxHitCount); - var quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift; - var value = GetColorValue(color); - - // If the color is rarely used on the image, - // then chances are that theres a better candidate, even if the saturation value - // is high. By multiplying the saturation value with a weight, we can lower - // it if the color is almost never used (hit count is low). - var satWeighted = quantSat; - var satWeight = balancedHitCount << 5; - if (satWeight < 0x100) - { - satWeighted = (satWeighted * satWeight) >> 8; - } - - // Compute score from saturation and dominance of the color. - // We prefer more vivid colors over dominant ones, so give more weight to the saturation. - var score = ((satWeighted << 1) + balancedHitCount) * value; - - return score; - } - - private static int BalanceHitCount(int hitCount, int maxHitCount) - { - return (hitCount << 8) / maxHitCount; - } - - private static int GetColorApproximateLuminosity(byte r, byte g, byte b) - { - return (r + g + b) / 3; - } - - private static int GetColorSaturation(PaletteColor color) - { - int cMax = Math.Max(Math.Max(color.R, color.G), color.B); - - if (cMax == 0) - { - return 0; - } - - int cMin = Math.Min(Math.Min(color.R, color.G), color.B); - int delta = cMax - cMin; - return (delta << 8) / cMax; - } - - private static int GetColorValue(PaletteColor color) - { - return Math.Max(Math.Max(color.R, color.G), color.B); - } - - private static int GetQuantizedColorKey(byte r, byte g, byte b) - { - int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; - int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; - return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml deleted file mode 100644 index 4def7c28..00000000 --- a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml +++ /dev/null @@ -1,205 +0,0 @@ -<window:StyleableWindow - x:Class="Ryujinx.Ava.UI.Windows.MainWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" - xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main" - Cursor="{Binding Cursor}" - Title="{Binding Title}" - WindowState="{Binding WindowState}" - Width="{Binding WindowWidth}" - Height="{Binding WindowHeight}" - MinWidth="1092" - MinHeight="672" - d:DesignHeight="720" - d:DesignWidth="1280" - x:DataType="viewModels:MainWindowViewModel" - mc:Ignorable="d" - WindowStartupLocation="Manual" - Focusable="True"> - <Window.Styles> - <Style Selector="TitleBar:fullscreen"> - <Setter Property="Background" Value="#000000" /> - </Style> - </Window.Styles> - <Design.DataContext> - <viewModels:MainWindowViewModel /> - </Design.DataContext> - <Window.Resources> - <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> - </Window.Resources> - <Window.KeyBindings> - <KeyBinding Gesture="Alt+Return" Command="{Binding ToggleFullscreen}" /> - <KeyBinding Gesture="F11" Command="{Binding ToggleFullscreen}" /> - <KeyBinding Gesture="Ctrl+Cmd+F" Command="{Binding ToggleFullscreen}" /> - <KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" /> - <KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" /> - </Window.KeyBindings> - <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - </Grid.RowDefinitions> - <helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> - <Grid - Grid.Row="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <StackPanel - Name="MenuBar" - MinHeight="35" - Grid.Row="0" - Margin="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - IsVisible="{Binding ShowMenuAndStatusBar}" - Orientation="Vertical"> - <main:MainMenuBarView - Name="MenuBarView" /> - </StackPanel> - <ContentControl - Name="MainContent" - Grid.Row="1" - Padding="0" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - BorderBrush="{DynamicResource ThemeControlBorderColor}" - BorderThickness="0,0,0,0" - DockPanel.Dock="Top" - IsVisible="{Binding ShowContent}"> - <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="GameLibrary"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - </Grid.RowDefinitions> - <main:MainViewControls - Name="ViewControls" - Grid.Row="0"/> - <controls:ApplicationListView - x:Name="ApplicationList" - Grid.Row="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalContentAlignment="Stretch" - VerticalContentAlignment="Stretch" - IsVisible="{Binding IsList}" /> - <controls:ApplicationGridView - x:Name="ApplicationGrid" - Grid.Row="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - HorizontalContentAlignment="Stretch" - VerticalContentAlignment="Stretch" - IsVisible="{Binding IsGrid}" /> - </Grid> - </ContentControl> - <Grid - Grid.Row="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Background="{DynamicResource ThemeContentBackgroundColor}" - IsVisible="{Binding ShowLoadProgress}" - Name="LoadingView" - ZIndex="1000"> - <Grid - Margin="40" - HorizontalAlignment="Center" - VerticalAlignment="Center" - IsVisible="{Binding ShowLoadProgress}"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <Border - Grid.RowSpan="2" - Grid.Column="0" - Width="256" - Height="256" - Margin="10" - Padding="4" - BorderBrush="Black" - BorderThickness="2" - BoxShadow="4 4 32 8 #40000000" - CornerRadius="3" - IsVisible="{Binding ShowLoadProgress}"> - <Image - Width="256" - Height="256" - IsVisible="{Binding ShowLoadProgress}" - Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" /> - </Border> - <Grid - Grid.Column="1" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - IsVisible="{Binding ShowLoadProgress}"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <TextBlock - Grid.Row="0" - Margin="10" - FontSize="30" - FontWeight="Bold" - IsVisible="{Binding ShowLoadProgress}" - Text="{Binding LoadHeading}" - TextAlignment="Start" - TextWrapping="Wrap" - MaxWidth="500" /> - <Border - Grid.Row="1" - Margin="10" - Padding="0" - HorizontalAlignment="Stretch" - BorderBrush="{Binding ProgressBarBackgroundColor}" - BorderThickness="1" - ClipToBounds="True" - CornerRadius="5" - IsVisible="{Binding ShowLoadProgress}"> - <ProgressBar - Height="10" - MinWidth="500" - Margin="0" - Padding="0" - HorizontalAlignment="Stretch" - ClipToBounds="True" - CornerRadius="5" - Foreground="{Binding ProgressBarForegroundColor}" - IsIndeterminate="{Binding IsLoadingIndeterminate}" - IsVisible="{Binding ShowLoadProgress}" - Maximum="{Binding ProgressMaximum}" - Minimum="0" - Value="{Binding ProgressValue}" /> - </Border> - <TextBlock - Grid.Row="2" - Margin="10" - FontSize="18" - IsVisible="{Binding ShowLoadProgress}" - Text="{Binding CacheLoadStatus}" - TextAlignment="Start" - MaxWidth="500" /> - </Grid> - </Grid> - </Grid> - <main:MainStatusBarView - Name="StatusBarView" - Grid.Row="2" /> - </Grid> - </Grid> -</window:StyleableWindow> diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs deleted file mode 100644 index 33a9af5b..00000000 --- a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs +++ /dev/null @@ -1,551 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Applet; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gpu; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.Input.HLE; -using Ryujinx.Input.SDL2; -using Ryujinx.Modules; -using Ryujinx.UI.App.Common; -using Ryujinx.UI.Common; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using System; -using System.IO; -using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class MainWindow : StyleableWindow - { - internal static MainWindowViewModel MainWindowViewModel { get; private set; } - - private bool _isLoading; - - private UserChannelPersistence _userChannelPersistence; - private static bool _deferLoad; - private static string _launchPath; - private static bool _startFullscreen; - internal readonly AvaHostUIHandler UiHandler; - - public VirtualFileSystem VirtualFileSystem { get; private set; } - public ContentManager ContentManager { get; private set; } - public AccountManager AccountManager { get; private set; } - - public LibHacHorizonManager LibHacHorizonManager { get; private set; } - - public InputManager InputManager { get; private set; } - - internal MainWindowViewModel ViewModel { get; private set; } - public SettingsWindow SettingsWindow { get; set; } - - public static bool ShowKeyErrorOnLoad { get; set; } - public ApplicationLibrary ApplicationLibrary { get; set; } - - public MainWindow() - { - ViewModel = new MainWindowViewModel(); - - MainWindowViewModel = ViewModel; - - DataContext = ViewModel; - - SetWindowSizePosition(); - - InitializeComponent(); - Load(); - - UiHandler = new AvaHostUIHandler(this); - - ViewModel.Title = $"Ryujinx {Program.Version}"; - - // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. - double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight; - Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; - Width /= Program.WindowScaleFactor; - - if (Program.PreviewerDetached) - { - InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); - - this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged); - this.ScalingChanged += OnScalingChanged; - } - } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - NotificationHelper.SetNotificationManager(this); - } - - private void IsActiveChanged(bool obj) - { - ViewModel.IsActive = obj; - } - - private void OnScalingChanged(object sender, EventArgs e) - { - Program.DesktopScaleFactor = this.RenderScaling; - } - - private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e) - { - Dispatcher.UIThread.Post(() => - { - ViewModel.Applications.Add(e.AppData); - }); - } - - private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) - { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound); - - Dispatcher.UIThread.Post(() => - { - ViewModel.StatusBarProgressValue = e.NumAppsLoaded; - ViewModel.StatusBarProgressMaximum = e.NumAppsFound; - - if (e.NumAppsFound == 0) - { - StatusBarView.LoadProgressBar.IsVisible = false; - } - - if (e.NumAppsLoaded == e.NumAppsFound) - { - StatusBarView.LoadProgressBar.IsVisible = false; - } - }); - } - - public void Application_Opened(object sender, ApplicationOpenedEventArgs args) - { - if (args.Application != null) - { - ViewModel.SelectedIcon = args.Application.Icon; - - string path = new FileInfo(args.Application.Path).FullName; - - ViewModel.LoadApplication(path).Wait(); - } - - args.Handled = true; - } - - internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg) - { - _deferLoad = true; - _launchPath = launchPathArg; - _startFullscreen = startFullscreenArg; - } - - public void SwitchToGameControl(bool startFullscreen = false) - { - ViewModel.ShowLoadProgress = false; - ViewModel.ShowContent = true; - ViewModel.IsLoadingIndeterminate = false; - - if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) - { - ViewModel.ToggleFullscreen(); - } - } - - public void ShowLoading(bool startFullscreen = false) - { - ViewModel.ShowContent = false; - ViewModel.ShowLoadProgress = true; - ViewModel.IsLoadingIndeterminate = true; - - if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) - { - ViewModel.ToggleFullscreen(); - } - } - - private void Initialize() - { - _userChannelPersistence = new UserChannelPersistence(); - VirtualFileSystem = VirtualFileSystem.CreateInstance(); - LibHacHorizonManager = new LibHacHorizonManager(); - ContentManager = new ContentManager(VirtualFileSystem); - - LibHacHorizonManager.InitializeFsServer(VirtualFileSystem); - LibHacHorizonManager.InitializeArpServer(); - LibHacHorizonManager.InitializeBcatServer(); - LibHacHorizonManager.InitializeSystemClients(); - - ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem); - - // Save data created before we supported extra data in directory save data will not work properly if - // given empty extra data. Luckily some of that extra data can be created using the data from the - // save data indexer, which should be enough to check access permissions for user saves. - // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. - // Consider removing this at some point in the future when we don't need to worry about old saves. - VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient); - - AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile); - - VirtualFileSystem.ReloadKeySet(); - - ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient); - } - - [SupportedOSPlatform("linux")] - private static async Task ShowVmMaxMapCountWarning() - { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary, - LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary], - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary] - ); - } - - [SupportedOSPlatform("linux")] - private static async Task ShowVmMaxMapCountDialog() - { - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary, - LinuxHelper.RecommendedVmMaxMapCount); - - UserResult response = await ContentDialogHelper.ShowTextDialog( - $"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}", - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary], - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary], - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart], - LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - (int)Symbol.Help - ); - - int rc; - - switch (response) - { - case UserResult.Ok: - rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); - } - break; - case UserResult.No: - rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); - } - break; - } - } - - private async Task CheckLaunchState() - { - if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) - { - Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})"); - - if (LinuxHelper.PkExecPath is not null) - { - await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog); - } - else - { - await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning); - } - } - - if (!ShowKeyErrorOnLoad) - { - if (_deferLoad) - { - _deferLoad = false; - - ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait(); - } - } - else - { - ShowKeyErrorOnLoad = false; - - await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); - } - - if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) - { - await Updater.BeginParse(this, false).ContinueWith(task => - { - Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); - }, TaskContinuationOptions.OnlyOnFaulted); - } - } - - private void Load() - { - StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged; - - ApplicationGrid.ApplicationOpened += Application_Opened; - - ApplicationGrid.DataContext = ViewModel; - - ApplicationList.ApplicationOpened += Application_Opened; - - ApplicationList.DataContext = ViewModel; - } - - private void SetWindowSizePosition() - { - PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, - ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); - - ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; - ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; - - ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal; - - if (CheckScreenBounds(savedPoint)) - { - Position = savedPoint; - } - else - { - WindowStartupLocation = WindowStartupLocation.CenterScreen; - } - } - - private bool CheckScreenBounds(PixelPoint configPoint) - { - for (int i = 0; i < Screens.ScreenCount; i++) - { - if (Screens.All[i].Bounds.Contains(configPoint)) - { - return true; - } - } - - Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center."); - return false; - } - - private void SaveWindowSizePosition() - { - ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height; - ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width; - - ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X; - ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; - - ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized; - - MainWindowViewModel.SaveConfig(); - } - - protected override void OnOpened(EventArgs e) - { - base.OnOpened(e); - - Initialize(); - - ViewModel.Initialize( - ContentManager, - StorageProvider, - ApplicationLibrary, - VirtualFileSystem, - AccountManager, - InputManager, - _userChannelPersistence, - LibHacHorizonManager, - UiHandler, - ShowLoading, - SwitchToGameControl, - SetMainContent, - this); - - ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; - ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; - - ViewModel.RefreshFirmwareStatus(); - - LoadApplications(); - -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - CheckLaunchState(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - - private void SetMainContent(Control content = null) - { - content ??= GameLibrary; - - if (MainContent.Content != content) - { - MainContent.Content = content; - } - } - - public static void UpdateGraphicsConfig() - { -#pragma warning disable IDE0055 // Disable formatting - GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale; - GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; - GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; - GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; - GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; - GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; -#pragma warning restore IDE0055 - } - - private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e) - { - var volumeSplitButton = sender as ToggleSplitButton; - if (ViewModel.IsGameRunning) - { - if (!volumeSplitButton.IsChecked) - { - ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute); - } - else - { - ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume(); - ViewModel.AppHost.Device.SetVolume(0); - } - - ViewModel.Volume = ViewModel.AppHost.Device.GetVolume(); - } - } - - protected override void OnClosing(WindowClosingEventArgs e) - { - if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit) - { - e.Cancel = true; - - ConfirmExit(); - - return; - } - - ViewModel.IsClosing = true; - - if (ViewModel.AppHost != null) - { - ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit; - ViewModel.AppHost.AppExit += (sender, e) => - { - ViewModel.AppHost = null; - - Dispatcher.UIThread.Post(() => - { - MainContent = null; - - Close(); - }); - }; - ViewModel.AppHost?.Stop(); - - e.Cancel = true; - - return; - } - - SaveWindowSizePosition(); - - ApplicationLibrary.CancelLoading(); - InputManager.Dispose(); - Program.Exit(); - - base.OnClosing(e); - } - - private void ConfirmExit() - { - Dispatcher.UIThread.InvokeAsync(async () => - { - ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog(); - - if (ViewModel.IsClosing) - { - Close(); - } - }); - } - - public void LoadApplications() - { - ViewModel.Applications.Clear(); - - StatusBarView.LoadProgressBar.IsVisible = true; - ViewModel.StatusBarProgressMaximum = 0; - ViewModel.StatusBarProgressValue = 0; - - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); - - ReloadGameList(); - } - - public void ToggleFileType(string fileType) - { - _ = fileType switch - { -#pragma warning disable IDE0055 // Disable formatting - "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 - }; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - LoadApplications(); - } - - private void ReloadGameList() - { - if (_isLoading) - { - return; - } - - _isLoading = true; - - Thread applicationLibraryThread = new(() => - { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); - - _isLoading = false; - }) - { - Name = "GUI.ApplicationLibraryThread", - IsBackground = true, - }; - applicationLibraryThread.Start(); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml deleted file mode 100644 index 0ed05ce3..00000000 --- a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml +++ /dev/null @@ -1,179 +0,0 @@ -<UserControl - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - Width="500" - Height="380" - mc:Ignorable="d" - x:Class="Ryujinx.Ava.UI.Windows.ModManagerWindow" - x:CompileBindings="True" - x:DataType="viewModels:ModManagerViewModel" - Focusable="True"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Panel - Margin="0 0 0 10" - Grid.Row="0"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <TextBlock - Grid.Column="0" - Text="{Binding ModCount}" /> - <StackPanel - Margin="10 0" - Grid.Column="1" - Orientation="Horizontal"> - <Button - Name="EnableAllButton" - MinWidth="90" - Margin="5" - Command="{Binding EnableAll}"> - <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> - </Button> - <Button - Name="DisableAllButton" - MinWidth="90" - Margin="5" - Command="{Binding DisableAll}"> - <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> - </Button> - </StackPanel> - <TextBox - Grid.Column="2" - MinHeight="27" - MaxHeight="27" - HorizontalAlignment="Stretch" - Watermark="{locale:Locale Search}" - Text="{Binding Search}" /> - </Grid> - </Panel> - <Border - Grid.Row="1" - Margin="0 0 0 24" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1" - CornerRadius="5" - Padding="2.5"> - <ListBox - AutoScrollToSelectedItem="False" - SelectionMode="Multiple, Toggle" - Background="Transparent" - SelectionChanged="OnSelectionChanged" - SelectedItems="{Binding SelectedMods, Mode=TwoWay}" - ItemsSource="{Binding Views}"> - <ListBox.DataTemplates> - <DataTemplate - DataType="models:ModModel"> - <Panel Margin="10"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <TextBlock - HorizontalAlignment="Left" - VerticalAlignment="Center" - MaxLines="2" - TextWrapping="Wrap" - TextTrimming="CharacterEllipsis" - Text="{Binding Name}" /> - <StackPanel - Grid.Column="1" - Spacing="10" - Orientation="Horizontal" - HorizontalAlignment="Right"> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="OpenLocation"> - <ui:SymbolIcon - Symbol="OpenFolder" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="DeleteMod"> - <ui:SymbolIcon - Symbol="Cancel" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - </StackPanel> - </Grid> - </Panel> - </DataTemplate> - </ListBox.DataTemplates> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Background" Value="Transparent" /> - </Style> - </ListBox.Styles> - </ListBox> - </Border> - <Panel - Grid.Row="2" - HorizontalAlignment="Stretch"> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Left"> - <Button - Name="AddButton" - MinWidth="90" - Margin="5" - Command="{Binding Add}"> - <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> - </Button> - <Button - Name="RemoveAllButton" - MinWidth="90" - Margin="5" - Click="DeleteAll"> - <TextBlock Text="{locale:Locale ModManagerDeleteAllButton}" /> - </Button> - </StackPanel> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Right"> - <Button - Name="SaveButton" - MinWidth="90" - Margin="5" - Click="SaveAndClose"> - <TextBlock Text="{locale:Locale SettingsButtonSave}" /> - </Button> - <Button - Name="CancelButton" - MinWidth="90" - Margin="5" - Click="Close"> - <TextBlock Text="{locale:Locale InputDialogCancel}" /> - </Button> - </StackPanel> - </Panel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs deleted file mode 100644 index d9ae0d4f..00000000 --- a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Styling; -using FluentAvalonia.UI.Controls; -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 System.Threading.Tasks; -using Button = Avalonia.Controls.Button; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class ModManagerWindow : UserControl - { - public ModManagerViewModel ViewModel; - - public ModManagerWindow() - { - DataContext = this; - - InitializeComponent(); - } - - public ModManagerWindow(ulong titleId) - { - DataContext = ViewModel = new ModManagerViewModel(titleId); - - InitializeComponent(); - } - - public static async Task Show(ulong titleId, string titleName) - { - ContentDialog contentDialog = new() - { - PrimaryButtonText = "", - SecondaryButtonText = "", - CloseButtonText = "", - Content = new ModManagerWindow(titleId), - Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")), - }; - - Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); - bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); - - contentDialog.Styles.Add(bottomBorder); - - await contentDialog.ShowAsync(); - } - - private void SaveAndClose(object sender, RoutedEventArgs e) - { - ViewModel.Save(); - ((ContentDialog)Parent).Hide(); - } - - private void Close(object sender, RoutedEventArgs e) - { - ((ContentDialog)Parent).Hide(); - } - - private async void DeleteMod(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is ModModel model) - { - var result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogModManagerDeletionWarningMessage, model.Name), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - ViewModel.Delete(model); - } - } - } - } - - private async void DeleteAll(object sender, RoutedEventArgs e) - { - var result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance[LocaleKeys.DialogModManagerDeletionAllWarningMessage], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - ViewModel.DeleteAll(); - } - } - - private void OpenLocation(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is ModModel model) - { - OpenHelper.OpenFolder(model.Path); - } - } - } - - private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - foreach (var content in e.AddedItems) - { - if (content is ModModel model) - { - var index = ViewModel.Mods.IndexOf(model); - - if (index != -1) - { - ViewModel.Mods[index].Enabled = true; - } - } - } - - foreach (var content in e.RemovedItems) - { - if (content is ModModel model) - { - var index = ViewModel.Mods.IndexOf(model); - - if (index != -1) - { - ViewModel.Mods[index].Enabled = false; - } - } - } - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml b/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml deleted file mode 100644 index 40cac90d..00000000 --- a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml +++ /dev/null @@ -1,130 +0,0 @@ -<window:StyleableWindow - x:Class="Ryujinx.Ava.UI.Windows.SettingsWindow" - xmlns="https://github.com/avaloniaui" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings" - xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" - Width="1100" - Height="768" - MinWidth="800" - MinHeight="480" - WindowStartupLocation="CenterOwner" - x:DataType="viewModels:SettingsViewModel" - mc:Ignorable="d" - Focusable="True"> - <Design.DataContext> - <viewModels:SettingsViewModel /> - </Design.DataContext> - <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600"> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <ContentPresenter - x:Name="ContentPresenter" - Grid.Row="1" - IsVisible="False" - KeyboardNavigation.IsTabStop="False"/> - <Grid Name="Pages" IsVisible="False" Grid.Row="2"> - <settings:SettingsUiView Name="UiPage" /> - <settings:SettingsInputView Name="InputPage" /> - <settings:SettingsHotkeysView Name="HotkeysPage" /> - <settings:SettingsSystemView Name="SystemPage" /> - <settings:SettingsCPUView Name="CpuPage" /> - <settings:SettingsGraphicsView Name="GraphicsPage" /> - <settings:SettingsAudioView Name="AudioPage" /> - <settings:SettingsNetworkView Name="NetworkPage" /> - <settings:SettingsLoggingView Name="LoggingPage" /> - </Grid> - <ui:NavigationView - Grid.Row="1" - IsSettingsVisible="False" - Name="NavPanel" - IsBackEnabled="False" - PaneDisplayMode="Left" - Margin="2,10,10,0" - VerticalAlignment="Stretch" - HorizontalAlignment="Stretch" - OpenPaneLength="200"> - <ui:NavigationView.MenuItems> - <ui:NavigationViewItem - IsSelected="True" - Content="{locale:Locale SettingsTabGeneral}" - Tag="UiPage" - IconSource="New" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabInput}" - Tag="InputPage" - IconSource="Games" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabHotkeys}" - Tag="HotkeysPage" - IconSource="Keyboard" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabSystem}" - Tag="SystemPage" - IconSource="Settings" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabCpu}" - Tag="CpuPage"> - <ui:NavigationViewItem.IconSource> - <ui:FontIconSource - FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons" - Glyph="{helpers:GlyphValueConverter Chip}" /> - </ui:NavigationViewItem.IconSource> - </ui:NavigationViewItem> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabGraphics}" - Tag="GraphicsPage" - IconSource="Image" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabAudio}" - IconSource="Audio" - Tag="AudioPage" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabNetwork}" - Tag="NetworkPage" - IconSource="Globe" /> - <ui:NavigationViewItem - Content="{locale:Locale SettingsTabLogging}" - Tag="LoggingPage" - IconSource="Document" /> - </ui:NavigationView.MenuItems> - <ui:NavigationView.Styles> - <Style Selector="Grid#PlaceholderGrid"> - <Setter Property="Height" Value="40" /> - </Style> - <Style Selector="ui|NavigationViewItem ui|SymbolIcon"> - <Setter Property="FlowDirection" Value="LeftToRight" /> - </Style> - </ui:NavigationView.Styles> - </ui:NavigationView> - <ReversibleStackPanel - Grid.Row="2" - Margin="10" - Spacing="10" - Orientation="Horizontal" - HorizontalAlignment="Right" - ReverseOrder="{Binding IsMacOS}"> - <Button - HotKey="Enter" - Classes="accent" - Content="{locale:Locale SettingsButtonOk}" - Command="{Binding OkButton}" /> - <Button - HotKey="Escape" - Content="{locale:Locale SettingsButtonCancel}" - Command="{Binding CancelButton}" /> - <Button - Content="{locale:Locale SettingsButtonApply}" - Command="{Binding ApplyButton}" /> - </ReversibleStackPanel> - </Grid> -</window:StyleableWindow> diff --git a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs deleted file mode 100644 index d7bb0b88..00000000 --- a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Avalonia.Controls; -using FluentAvalonia.Core; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.HLE.FileSystem; -using System; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class SettingsWindow : StyleableWindow - { - internal SettingsViewModel ViewModel { get; set; } - - public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager) - { - Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}"; - - ViewModel = new SettingsViewModel(virtualFileSystem, contentManager); - DataContext = ViewModel; - - ViewModel.CloseWindow += Close; - ViewModel.SaveSettingsEvent += SaveSettings; - - InitializeComponent(); - Load(); - } - - public SettingsWindow() - { - ViewModel = new SettingsViewModel(); - DataContext = ViewModel; - - InitializeComponent(); - Load(); - } - - public void SaveSettings() - { - InputPage.ControllerSettings?.SaveCurrentProfile(); - - if (Owner is MainWindow window && ViewModel.DirectoryChanged) - { - window.LoadApplications(); - } - } - - private void Load() - { - Pages.Children.Clear(); - NavPanel.SelectionChanged += NavPanelOnSelectionChanged; - NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0); - } - - private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e) - { - if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null) - { - switch (navItem.Tag.ToString()) - { - case "UiPage": - UiPage.ViewModel = ViewModel; - NavPanel.Content = UiPage; - break; - case "InputPage": - NavPanel.Content = InputPage; - break; - case "HotkeysPage": - NavPanel.Content = HotkeysPage; - break; - case "SystemPage": - SystemPage.ViewModel = ViewModel; - NavPanel.Content = SystemPage; - break; - case "CpuPage": - NavPanel.Content = CpuPage; - break; - case "GraphicsPage": - NavPanel.Content = GraphicsPage; - break; - case "AudioPage": - NavPanel.Content = AudioPage; - break; - case "NetworkPage": - NavPanel.Content = NetworkPage; - break; - case "LoggingPage": - NavPanel.Content = LoggingPage; - break; - default: - throw new NotImplementedException(); - } - } - } - - protected override void OnClosing(WindowClosingEventArgs e) - { - HotkeysPage.Dispose(); - InputPage.Dispose(); - base.OnClosing(e); - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs b/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs deleted file mode 100644 index a12d2b3e..00000000 --- a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Platform; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.UI.Common.Configuration; -using System.IO; -using System.Reflection; - -namespace Ryujinx.Ava.UI.Windows -{ - public class StyleableWindow : Window - { - public Bitmap IconImage { get; set; } - - public StyleableWindow() - { - WindowStartupLocation = WindowStartupLocation.CenterOwner; - TransparencyLevelHint = new[] { WindowTransparencyLevel.None }; - - using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - Icon = new WindowIcon(stream); - stream.Position = 0; - IconImage = new Bitmap(stream); - - LocaleManager.Instance.LocaleChanged += LocaleChanged; - LocaleChanged(); - } - - private void LocaleChanged() - { - FlowDirection = LocaleManager.Instance.IsRTL() ? FlowDirection.RightToLeft : FlowDirection.LeftToRight; - } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; - } - } -} diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml deleted file mode 100644 index 3eff389f..00000000 --- a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml +++ /dev/null @@ -1,133 +0,0 @@ -<UserControl - x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow" - xmlns="https://github.com/avaloniaui" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" - xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" - xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" - Width="500" - Height="300" - mc:Ignorable="d" - x:DataType="viewModels:TitleUpdateViewModel" - Focusable="True"> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Border - Grid.Row="0" - Margin="0 0 0 24" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - BorderBrush="{DynamicResource AppListHoverBackgroundColor}" - BorderThickness="1" - CornerRadius="5" - Padding="2.5"> - <ListBox - Background="Transparent" - SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}" - ItemsSource="{Binding Views}"> - <ListBox.DataTemplates> - <DataTemplate - DataType="models:TitleUpdateModel"> - <Panel Margin="10"> - <TextBlock - HorizontalAlignment="Left" - VerticalAlignment="Center" - TextWrapping="Wrap" - Text="{Binding Label}" /> - <StackPanel - Spacing="10" - Orientation="Horizontal" - HorizontalAlignment="Right"> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="OpenLocation"> - <ui:SymbolIcon - Symbol="OpenFolder" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - <Button - VerticalAlignment="Center" - HorizontalAlignment="Right" - Padding="10" - MinWidth="0" - MinHeight="0" - Click="RemoveUpdate"> - <ui:SymbolIcon - Symbol="Cancel" - HorizontalAlignment="Center" - VerticalAlignment="Center" /> - </Button> - </StackPanel> - </Panel> - </DataTemplate> - <DataTemplate - DataType="viewModels:BaseModel"> - <Panel - Height="33" - Margin="10"> - <TextBlock - HorizontalAlignment="Left" - VerticalAlignment="Center" - TextWrapping="Wrap" - Text="{locale:Locale NoUpdate}" /> - </Panel> - </DataTemplate> - </ListBox.DataTemplates> - <ListBox.Styles> - <Style Selector="ListBoxItem"> - <Setter Property="Background" Value="Transparent" /> - </Style> - </ListBox.Styles> - </ListBox> - </Border> - <Panel - Grid.Row="1" - HorizontalAlignment="Stretch"> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Left"> - <Button - Name="AddButton" - MinWidth="90" - Command="{Binding Add}"> - <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> - </Button> - <Button - Name="RemoveAllButton" - MinWidth="90" - Click="RemoveAll"> - <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> - </Button> - </StackPanel> - <StackPanel - Orientation="Horizontal" - Spacing="10" - HorizontalAlignment="Right"> - <Button - Name="SaveButton" - MinWidth="90" - Click="Save"> - <TextBlock Text="{locale:Locale SettingsButtonSave}" /> - </Button> - <Button - Name="CancelButton" - MinWidth="90" - Click="Close"> - <TextBlock Text="{locale:Locale InputDialogCancel}" /> - </Button> - </StackPanel> - </Panel> - </Grid> -</UserControl> diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs deleted file mode 100644 index f3ac6960..00000000 --- a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Styling; -using FluentAvalonia.UI.Controls; -using Ryujinx.Ava.Common.Locale; -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 System.Threading.Tasks; -using Button = Avalonia.Controls.Button; - -namespace Ryujinx.Ava.UI.Windows -{ - public partial class TitleUpdateWindow : UserControl - { - public TitleUpdateViewModel ViewModel; - - public TitleUpdateWindow() - { - DataContext = this; - - InitializeComponent(); - } - - public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId) - { - DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId); - - InitializeComponent(); - } - - public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) - { - ContentDialog contentDialog = new() - { - PrimaryButtonText = "", - SecondaryButtonText = "", - CloseButtonText = "", - Content = new TitleUpdateWindow(virtualFileSystem, titleId), - Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")), - }; - - Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); - bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); - - contentDialog.Styles.Add(bottomBorder); - - await ContentDialogHelper.ShowAsync(contentDialog); - } - - private void Close(object sender, RoutedEventArgs e) - { - ((ContentDialog)Parent).Hide(); - } - - public void Save(object sender, RoutedEventArgs e) - { - ViewModel.Save(); - - if (VisualRoot is MainWindow window) - { - window.LoadApplications(); - } - - ((ContentDialog)Parent).Hide(); - } - - private void OpenLocation(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - if (button.DataContext is TitleUpdateModel model) - { - OpenHelper.LocateFile(model.Path); - } - } - } - - private void RemoveUpdate(object sender, RoutedEventArgs e) - { - if (sender is Button button) - { - ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext); - } - } - - private void RemoveAll(object sender, RoutedEventArgs e) - { - ViewModel.TitleUpdates.Clear(); - - ViewModel.SortUpdates(); - } - } -} diff --git a/src/Ryujinx.Ava/app.manifest b/src/Ryujinx.Ava/app.manifest deleted file mode 100644 index 920136fb..00000000 --- a/src/Ryujinx.Ava/app.manifest +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> - <assemblyIdentity version="1.0.0.0" name="Ryujinx.Emulator.Avalonia"/> - <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> - <application> - <!-- Windows 10 & 11 --> - <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> - </application> - </compatibility> -</assembly> diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs new file mode 100644 index 00000000..ff7a2c3b --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs @@ -0,0 +1,205 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System; +using System.Collections.Generic; +using System.Numerics; +using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3Keyboard : IKeyboard + { + private class ButtonMappingEntry + { + public readonly GamepadButtonInputId To; + public readonly Key From; + + public ButtonMappingEntry(GamepadButtonInputId to, Key from) + { + To = to; + From = from; + } + } + + private readonly object _userMappingLock = new(); + + private readonly GTK3KeyboardDriver _driver; + private StandardKeyboardInputConfig _configuration; + private readonly List<ButtonMappingEntry> _buttonsUserMapping; + + public GTK3Keyboard(GTK3KeyboardDriver driver, string id, string name) + { + _driver = driver; + Id = id; + Name = name; + _buttonsUserMapping = new List<ButtonMappingEntry>(); + } + + private bool HasConfiguration => _configuration != null; + + public string Id { get; } + + public string Name { get; } + + public bool IsConnected => true; + + public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; + + public void Dispose() + { + // No operations + GC.SuppressFinalize(this); + } + + public KeyboardStateSnapshot GetKeyboardStateSnapshot() + { + return IKeyboard.GetStateSnapshot(this); + } + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig) + { + short stickX = 0; + short stickY = 0; + + if (snapshot.IsPressed((Key)stickConfig.StickUp)) + { + stickY += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickDown)) + { + stickY -= 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickRight)) + { + stickX += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + { + stickX -= 1; + } + + OpenTK.Mathematics.Vector2 stick = new(stickX, stickY); + + stick.NormalizeFast(); + + return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (!HasConfiguration) + { + return result; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + continue; + } + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + + result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); + result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); + } + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotSupportedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(Key key) + { + return _driver.IsPressed(key); + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardKeyboardInputConfig)configuration; + + _buttonsUserMapping.Clear(); + + // Then left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + + // Finally right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); + } + } + + public void SetTriggerThreshold(float triggerThreshold) + { + // No operations + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + // No operations + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + // No operations + + return Vector3.Zero; + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs new file mode 100644 index 00000000..e502254b --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs @@ -0,0 +1,94 @@ +using Gdk; +using Gtk; +using System; +using System.Collections.Generic; +using GtkKey = Gdk.Key; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3KeyboardDriver : IGamepadDriver + { + private readonly Widget _widget; + private readonly HashSet<GtkKey> _pressedKeys; + + public GTK3KeyboardDriver(Widget widget) + { + _widget = widget; + _pressedKeys = new HashSet<GtkKey>(); + + _widget.KeyPressEvent += OnKeyPress; + _widget.KeyReleaseEvent += OnKeyRelease; + } + + public string DriverName => "GTK3"; + + private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; + + public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; + + public event Action<string> OnGamepadConnected + { + add { } + remove { } + } + + public event Action<string> OnGamepadDisconnected + { + add { } + remove { } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _widget.KeyPressEvent -= OnKeyPress; + _widget.KeyReleaseEvent -= OnKeyRelease; + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + [GLib.ConnectBefore] + protected void OnKeyPress(object sender, KeyPressEventArgs args) + { + GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); + + _pressedKeys.Add(key); + } + + [GLib.ConnectBefore] + protected void OnKeyRelease(object sender, KeyReleaseEventArgs args) + { + GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); + + _pressedKeys.Remove(key); + } + + internal bool IsPressed(Key key) + { + if (key == Key.Unbound || key == Key.Unknown) + { + return false; + } + + GtkKey nativeKey = GTK3MappingHelper.ToGtkKey(key); + + return _pressedKeys.Contains(nativeKey); + } + + public IGamepad GetGamepad(string id) + { + if (!_keyboardIdentifers[0].Equals(id)) + { + return null; + } + + return new GTK3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs new file mode 100644 index 00000000..422a9603 --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using GtkKey = Gdk.Key; + +namespace Ryujinx.Input.GTK3 +{ + public static class GTK3MappingHelper + { + private static readonly GtkKey[] _keyMapping = new GtkKey[(int)Key.Count] + { + // NOTE: invalid + GtkKey.blank, + + GtkKey.Shift_L, + GtkKey.Shift_R, + GtkKey.Control_L, + GtkKey.Control_R, + GtkKey.Alt_L, + GtkKey.Alt_R, + GtkKey.Super_L, + GtkKey.Super_R, + GtkKey.Menu, + GtkKey.F1, + GtkKey.F2, + GtkKey.F3, + GtkKey.F4, + GtkKey.F5, + GtkKey.F6, + GtkKey.F7, + GtkKey.F8, + GtkKey.F9, + GtkKey.F10, + GtkKey.F11, + GtkKey.F12, + GtkKey.F13, + GtkKey.F14, + GtkKey.F15, + GtkKey.F16, + GtkKey.F17, + GtkKey.F18, + GtkKey.F19, + GtkKey.F20, + GtkKey.F21, + GtkKey.F22, + GtkKey.F23, + GtkKey.F24, + GtkKey.F25, + GtkKey.F26, + GtkKey.F27, + GtkKey.F28, + GtkKey.F29, + GtkKey.F30, + GtkKey.F31, + GtkKey.F32, + GtkKey.F33, + GtkKey.F34, + GtkKey.F35, + GtkKey.Up, + GtkKey.Down, + GtkKey.Left, + GtkKey.Right, + GtkKey.Return, + GtkKey.Escape, + GtkKey.space, + GtkKey.Tab, + GtkKey.BackSpace, + GtkKey.Insert, + GtkKey.Delete, + GtkKey.Page_Up, + GtkKey.Page_Down, + GtkKey.Home, + GtkKey.End, + GtkKey.Caps_Lock, + GtkKey.Scroll_Lock, + GtkKey.Print, + GtkKey.Pause, + GtkKey.Num_Lock, + GtkKey.Clear, + GtkKey.KP_0, + GtkKey.KP_1, + GtkKey.KP_2, + GtkKey.KP_3, + GtkKey.KP_4, + GtkKey.KP_5, + GtkKey.KP_6, + GtkKey.KP_7, + GtkKey.KP_8, + GtkKey.KP_9, + GtkKey.KP_Divide, + GtkKey.KP_Multiply, + GtkKey.KP_Subtract, + GtkKey.KP_Add, + GtkKey.KP_Decimal, + GtkKey.KP_Enter, + GtkKey.a, + GtkKey.b, + GtkKey.c, + GtkKey.d, + GtkKey.e, + GtkKey.f, + GtkKey.g, + GtkKey.h, + GtkKey.i, + GtkKey.j, + GtkKey.k, + GtkKey.l, + GtkKey.m, + GtkKey.n, + GtkKey.o, + GtkKey.p, + GtkKey.q, + GtkKey.r, + GtkKey.s, + GtkKey.t, + GtkKey.u, + GtkKey.v, + GtkKey.w, + GtkKey.x, + GtkKey.y, + GtkKey.z, + GtkKey.Key_0, + GtkKey.Key_1, + GtkKey.Key_2, + GtkKey.Key_3, + GtkKey.Key_4, + GtkKey.Key_5, + GtkKey.Key_6, + GtkKey.Key_7, + GtkKey.Key_8, + GtkKey.Key_9, + GtkKey.grave, + GtkKey.grave, + GtkKey.minus, + GtkKey.plus, + GtkKey.bracketleft, + GtkKey.bracketright, + GtkKey.semicolon, + GtkKey.quoteright, + GtkKey.comma, + GtkKey.period, + GtkKey.slash, + GtkKey.backslash, + + // NOTE: invalid + GtkKey.blank, + }; + + private static readonly Dictionary<GtkKey, Key> _gtkKeyMapping; + + static GTK3MappingHelper() + { + var inputKeys = Enum.GetValues<Key>().SkipLast(1); + + // GtkKey is not contiguous and quite large, so use a dictionary instead of an array. + _gtkKeyMapping = new Dictionary<GtkKey, Key>(); + + foreach (var key in inputKeys) + { + var index = ToGtkKey(key); + _gtkKeyMapping[index] = key; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GtkKey ToGtkKey(Key key) + { + return _keyMapping[(int)key]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Key ToInputKey(GtkKey key) + { + return _gtkKeyMapping.GetValueOrDefault(key, Key.Unknown); + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs new file mode 100644 index 00000000..0ab817ec --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common.Configuration.Hid; +using System; +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3Mouse : IMouse + { + private GTK3MouseDriver _driver; + + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + + public string Id => "0"; + + public string Name => "GTKMouse"; + + public bool IsConnected => true; + + public bool[] Buttons => _driver.PressedButtons; + + public GTK3Mouse(GTK3MouseDriver driver) + { + _driver = driver; + } + + public Size ClientSize => _driver.GetClientSize(); + + public Vector2 GetPosition() + { + return _driver.CurrentPosition; + } + + public Vector2 GetScroll() + { + return _driver.Scroll; + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return _driver.IsButtonPressed(button); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _driver = null; + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs new file mode 100644 index 00000000..5962bcb2 --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs @@ -0,0 +1,108 @@ +using Gdk; +using Gtk; +using System; +using System.Numerics; +using Size = System.Drawing.Size; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3MouseDriver : IGamepadDriver + { + private Widget _widget; + private bool _isDisposed; + + public bool[] PressedButtons { get; } + + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + + public GTK3MouseDriver(Widget parent) + { + _widget = parent; + + _widget.MotionNotifyEvent += Parent_MotionNotifyEvent; + _widget.ButtonPressEvent += Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent; + _widget.ScrollEvent += Parent_ScrollEvent; + + PressedButtons = new bool[(int)MouseButton.Count]; + } + + + [GLib.ConnectBefore] + private void Parent_ScrollEvent(object o, ScrollEventArgs args) + { + Scroll = new Vector2((float)args.Event.X, (float)args.Event.Y); + } + + [GLib.ConnectBefore] + private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + PressedButtons[args.Event.Button - 1] = false; + } + + [GLib.ConnectBefore] + private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args) + { + PressedButtons[args.Event.Button - 1] = true; + } + + [GLib.ConnectBefore] + private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args) + { + if (args.Event.Device.InputSource == InputSource.Mouse) + { + CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y); + } + } + + public bool IsButtonPressed(MouseButton button) + { + return PressedButtons[(int)button]; + } + + public Size GetClientSize() + { + return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight); + } + + public string DriverName => "GTK3"; + + public event Action<string> OnGamepadConnected + { + add { } + remove { } + } + + public event Action<string> OnGamepadDisconnected + { + add { } + remove { } + } + + public ReadOnlySpan<string> GamepadsIds => new[] { "0" }; + + public IGamepad GetGamepad(string id) + { + return new GTK3Mouse(this); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + GC.SuppressFinalize(this); + + _isDisposed = true; + + _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent; + _widget.ButtonPressEvent -= Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent; + + _widget = null; + } + } +} diff --git a/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs new file mode 100644 index 00000000..43bde942 --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs @@ -0,0 +1,95 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.UI; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.Diagnostics; +using System.Reflection; + +namespace Ryujinx.Modules +{ + public class UpdateDialog : Gtk.Window + { +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [Builder.Object] public Label MainText; + [Builder.Object] public Label SecondaryText; + [Builder.Object] public LevelBar ProgressBar; + [Builder.Object] public Button YesButton; + [Builder.Object] public Button NoButton; +#pragma warning restore CS0649, IDE0044 + + private readonly MainWindow _mainWindow; + private readonly string _buildUrl; + private bool _restartQuery; + + public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Gtk3.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } + + private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) + { + builder.Autoconnect(this); + + _mainWindow = mainWindow; + _buildUrl = buildUrl; + + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); + MainText.Text = "Do you want to update Ryujinx to the latest version?"; + SecondaryText.Text = $"{Program.Version} -> {newVersion}"; + + ProgressBar.Hide(); + + YesButton.Clicked += YesButton_Clicked; + NoButton.Clicked += NoButton_Clicked; + } + + private void YesButton_Clicked(object sender, EventArgs args) + { + if (_restartQuery) + { + string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + + Environment.Exit(0); + } + else + { + Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; + _mainWindow.ExitMenuItem.Sensitive = false; + + YesButton.Hide(); + NoButton.Hide(); + ProgressBar.Show(); + + SecondaryText.Text = ""; + _restartQuery = true; + + Updater.UpdateRyujinx(this, _buildUrl); + } + } + + private void NoButton_Clicked(object sender, EventArgs args) + { + Updater.Running = false; + _mainWindow.Window.Functions = WMFunction.All; + + _mainWindow.ExitMenuItem.Sensitive = true; + _mainWindow.UpdateMenuItem.Sensitive = true; + + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade new file mode 100644 index 00000000..cc80167e --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkWindow" id="UpdateDialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - Updater</property> + <property name="resizable">False</property> + <property name="window_position">center</property> + <property name="default_width">400</property> + <property name="default_height">130</property> + <child> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="MainBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="BodyBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="MainText"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="size" value="10000"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="SecondaryText"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLevelBar" id="ProgressBar"> + <property name="height_request">20</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="max_value">100</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="ButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="YesButton"> + <property name="label" translatable="yes">Yes</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">5</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="NoButton"> + <property name="label" translatable="yes">No</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_left">5</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs new file mode 100644 index 00000000..8b006f63 --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs @@ -0,0 +1,622 @@ +using Gtk; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using ICSharpCode.SharpZipLib.Zip; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI; +using Ryujinx.UI.Common.Models.Github; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + public static class Updater + { + private const string GitHubApiUrl = "https://api.github.com"; + private const int ConnectionCount = 4; + + internal static bool Running; + + private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; + private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); + private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); + + private static string _buildVer; + private static string _platformExt; + private static string _buildUrl; + private static long _buildSize; + + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. + private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" }; + + private static HttpClient ConstructHttpClient() + { + HttpClient result = new(); + + // Required by GitHub to interact with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; + } + + public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) + { + if (Running) + { + return; + } + + Running = true; + mainWindow.UpdateMenuItem.Sensitive = false; + + int artifactIndex = -1; + + // Detect current platform + if (OperatingSystem.IsMacOS()) + { + _platformExt = "osx_x64.zip"; + artifactIndex = 1; + } + else if (OperatingSystem.IsWindows()) + { + _platformExt = "win_x64.zip"; + artifactIndex = 2; + } + else if (OperatingSystem.IsLinux()) + { + var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; + _platformExt = $"linux_{arch}.tar.gz"; + artifactIndex = 0; + } + + if (artifactIndex == -1) + { + GtkDialog.CreateErrorDialog("Your platform is not supported!"); + + return; + } + + Version newVersion; + Version currentVersion; + + try + { + currentVersion = Version.Parse(Program.Version); + } + catch + { + GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); + + return; + } + + // Get latest version number from GitHub API + try + { + using HttpClient jsonClient = ConstructHttpClient(); + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + + // Fetch latest build information + string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); + var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); + _buildVer = fetched.Name; + + foreach (var asset in fetched.Assets) + { + if (asset.Name.StartsWith("gtk-ryujinx") && asset.Name.EndsWith(_platformExt)) + { + _buildUrl = asset.BrowserDownloadUrl; + + if (asset.State != "uploaded") + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + return; + } + + break; + } + } + + if (_buildUrl == null) + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + return; + } + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, exception.Message); + GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); + + return; + } + + try + { + newVersion = Version.Parse(_buildVer); + } + catch + { + GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); + + return; + } + + if (newVersion <= currentVersion) + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + Running = false; + mainWindow.UpdateMenuItem.Sensitive = true; + + return; + } + + // Fetch build size information to learn chunk sizes. + using HttpClient buildSizeClient = ConstructHttpClient(); + try + { + buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); + + HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); + + _buildSize = message.Content.Headers.ContentRange.Length.Value; + } + catch (Exception ex) + { + Logger.Warning?.Print(LogClass.Application, ex.Message); + Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater"); + + _buildSize = -1; + } + + // Show a message asking the user if they want to update + UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl); + updateDialog.Show(); + } + + public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) + { + // Empty update dir, although it shouldn't ever have anything inside it + if (Directory.Exists(_updateDir)) + { + Directory.Delete(_updateDir, true); + } + + Directory.CreateDirectory(_updateDir); + + string updateFile = Path.Combine(_updateDir, "update.bin"); + + // Download the update .zip + updateDialog.MainText.Text = "Downloading Update..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = 100; + + if (_buildSize >= 0) + { + DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile); + } + else + { + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + } + } + + private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + // Multi-Threaded Updater + long chunkSize = _buildSize / ConnectionCount; + long remainderChunk = _buildSize % ConnectionCount; + + int completedRequests = 0; + int totalProgressPercentage = 0; + int[] progressPercentage = new int[ConnectionCount]; + + List<byte[]> list = new(ConnectionCount); + List<WebClient> webClients = new(ConnectionCount); + + for (int i = 0; i < ConnectionCount; i++) + { + list.Add(Array.Empty<byte>()); + } + + for (int i = 0; i < ConnectionCount; i++) + { +#pragma warning disable SYSLIB0014 + // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. + using WebClient client = new(); +#pragma warning restore SYSLIB0014 + webClients.Add(client); + + if (i == ConnectionCount - 1) + { + client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}"); + } + else + { + client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}"); + } + + client.DownloadProgressChanged += (_, args) => + { + int index = (int)args.UserState; + + Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]); + Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); + Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); + + updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount; + }; + + client.DownloadDataCompleted += (_, args) => + { + int index = (int)args.UserState; + + if (args.Cancelled) + { + webClients[index].Dispose(); + + return; + } + + list[index] = args.Result; + Interlocked.Increment(ref completedRequests); + + if (Equals(completedRequests, ConnectionCount)) + { + byte[] mergedFileBytes = new byte[_buildSize]; + for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++) + { + Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length); + destinationOffset += list[connectionIndex].Length; + } + + File.WriteAllBytes(updateFile, mergedFileBytes); + + try + { + InstallUpdate(updateDialog, updateFile); + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, e.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + + return; + } + } + }; + + try + { + client.DownloadDataAsync(new Uri(downloadUrl), i); + } + catch (WebException ex) + { + Logger.Warning?.Print(LogClass.Application, ex.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + + foreach (WebClient webClient in webClients) + { + webClient.CancelAsync(); + } + + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + + return; + } + } + } + + private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + using HttpClient client = new(); + // We do not want to timeout while downloading + client.Timeout = TimeSpan.FromDays(1); + + using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result; + using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result; + using Stream updateFileStream = File.Open(updateFile, FileMode.Create); + + long totalBytes = response.Content.Headers.ContentLength.Value; + long byteWritten = 0; + + byte[] buffer = new byte[32 * 1024]; + + while (true) + { + int readSize = remoteFileStream.Read(buffer); + + if (readSize == 0) + { + break; + } + + byteWritten += readSize; + + updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100; + updateFileStream.Write(buffer, 0, readSize); + } + + InstallUpdate(updateDialog, updateFile); + } + + private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile)) + { + Name = "Updater.SingleThreadWorker", + }; + worker.Start(); + } + + private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile) + { + // Extract Update + updateDialog.MainText.Text = "Extracting Update..."; + updateDialog.ProgressBar.Value = 0; + + if (OperatingSystem.IsLinux()) + { + using Stream inStream = File.OpenRead(updateFile); + using Stream gzipStream = new GZipInputStream(inStream); + using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); + updateDialog.ProgressBar.MaxValue = inStream.Length; + + await Task.Run(() => + { + TarEntry tarEntry; + + if (!OperatingSystem.IsWindows()) + { + while ((tarEntry = tarStream.GetNextEntry()) != null) + { + if (tarEntry.IsDirectory) + { + continue; + } + + string outPath = Path.Combine(_updateDir, tarEntry.Name); + + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + + using FileStream outStream = File.OpenWrite(outPath); + tarStream.CopyEntryContents(outStream); + + File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); + + TarEntry entry = tarEntry; + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value += entry.Size; + }); + } + } + }); + + updateDialog.ProgressBar.Value = inStream.Length; + } + else + { + using Stream inStream = File.OpenRead(updateFile); + using ZipFile zipFile = new(inStream); + updateDialog.ProgressBar.MaxValue = zipFile.Count; + + await Task.Run(() => + { + foreach (ZipEntry zipEntry in zipFile) + { + if (zipEntry.IsDirectory) + { + continue; + } + + string outPath = Path.Combine(_updateDir, zipEntry.Name); + + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + + using Stream zipStream = zipFile.GetInputStream(zipEntry); + using FileStream outStream = File.OpenWrite(outPath); + zipStream.CopyTo(outStream); + + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value++; + }); + } + }); + } + + // Delete downloaded zip + File.Delete(updateFile); + + List<string> allFiles = EnumerateFilesToDelete().ToList(); + + updateDialog.MainText.Text = "Renaming Old Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = allFiles.Count; + + // Replace old files + await Task.Run(() => + { + foreach (string file in allFiles) + { + try + { + File.Move(file, file + ".ryuold"); + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value++; + }); + } + catch + { + Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file); + } + } + + Application.Invoke(delegate + { + updateDialog.MainText.Text = "Adding New Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length; + }); + + MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog); + }); + + Directory.Delete(_updateDir, true); + + updateDialog.MainText.Text = "Update Complete!"; + updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?"; + updateDialog.Modal = true; + + updateDialog.ProgressBar.Hide(); + updateDialog.YesButton.Show(); + updateDialog.NoButton.Show(); + } + + public static bool CanUpdate(bool showWarnings) + { +#if !DISABLE_UPDATER + if (!NetworkInterface.GetIsNetworkAvailable()) + { + if (showWarnings) + { + GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!"); + } + + return false; + } + + if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) + { + if (showWarnings) + { + GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + } + + return false; + } + + return true; +#else + if (showWarnings) + { + if (ReleaseInformation.IsFlatHubBuild) + { + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); + } + else + { + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + } + } + + return false; +#endif + } + + // NOTE: This method should always reflect the latest build layout. + private static IEnumerable<string> EnumerateFilesToDelete() + { + var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. + + // Determine and exclude user files only when the updater is running, not when cleaning old files + if (Running) + { + // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. + var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename)); + + // Remove user files from the paths in files. + files = files.Except(userFiles); + } + + if (OperatingSystem.IsWindows()) + { + foreach (string dir in _windowsDependencyDirs) + { + string dirPath = Path.Combine(_homeDir, dir); + if (Directory.Exists(dirPath)) + { + files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories)); + } + } + } + + return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); + } + + private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog) + { + foreach (string directory in Directory.GetDirectories(root)) + { + string dirName = Path.GetFileName(directory); + + if (!Directory.Exists(Path.Combine(dest, dirName))) + { + Directory.CreateDirectory(Path.Combine(dest, dirName)); + } + + MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog); + } + + foreach (string file in Directory.GetFiles(root)) + { + File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); + + Application.Invoke(delegate + { + dialog.ProgressBar.Value++; + }); + } + } + + public static void CleanupUpdate() + { + foreach (string file in EnumerateFilesToDelete()) + { + if (Path.GetExtension(file).EndsWith(".ryuold")) + { + File.Delete(file); + } + } + } + } +} diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs new file mode 100644 index 00000000..1845c512 --- /dev/null +++ b/src/Ryujinx.Gtk3/Program.cs @@ -0,0 +1,378 @@ +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Modules; +using Ryujinx.SDL2.Common; +using Ryujinx.UI; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.SystemInfo; +using Ryujinx.UI.Widgets; +using SixLabors.ImageSharp.Formats.Jpeg; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx +{ + partial class Program + { + public static double WindowScaleFactor { get; private set; } + + public static string Version { get; private set; } + + public static string ConfigurationPath { get; set; } + + public static string CommandLineProfile { get; set; } + + private const string X11LibraryName = "libX11"; + + [LibraryImport(X11LibraryName)] + private static partial int XInitThreads(); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); + + [LibraryImport("libc", SetLastError = true)] + private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); + + private const uint MbIconWarning = 0x30; + + static Program() + { + if (OperatingSystem.IsLinux()) + { + NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) => + { + if (name != X11LibraryName) + { + return IntPtr.Zero; + } + + if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result)) + { + if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result)) + { + return IntPtr.Zero; + } + } + + return result; + }); + } + } + + static void Main(string[] args) + { + Version = ReleaseInformation.Version; + + if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) + { + MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + } + + // Parse arguments + CommandLineState.ParseArguments(args); + + // Hook unhandled exception and process exit events. + GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); + + // Make process DPI aware for proper window sizing on high-res screens. + ForceDpiAware.Windows(); + WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + + // Delete backup files after updating. + Task.Run(Updater.CleanupUpdate); + + Console.Title = $"Ryujinx Console {Version}"; + + // NOTE: GTK3 doesn't init X11 in a multi threaded way. + // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads). + if (OperatingSystem.IsLinux()) + { + if (XInitThreads() == 0) + { + throw new NotSupportedException("Failed to initialize multi-threading support."); + } + + Environment.SetEnvironmentVariable("GDK_BACKEND", "x11"); + setenv("GDK_BACKEND", "x11", 1); + } + + if (OperatingSystem.IsMacOS()) + { + string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); + string resourcesDataDir; + + if (Path.GetFileName(baseDirectory) == "MacOS") + { + resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources"); + } + else + { + resourcesDataDir = baseDirectory; + } + + static void SetEnvironmentVariableNoCaching(string key, string value) + { + int res = setenv(key, value, 1); + Debug.Assert(res != -1); + } + + // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. + SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + + // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. + SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + + SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); + } + + string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + + // Setup base data directory. + AppDataManager.Initialize(CommandLineState.BaseDirPathArg); + + // Initialize the configuration. + ConfigurationState.Initialize(); + + // Initialize the logger system. + LoggerModule.Initialize(); + + // Initialize Discord integration. + DiscordIntegrationModule.Initialize(); + + // Initialize SDL2 driver + SDL2Driver.MainThreadDispatcher = action => + { + Application.Invoke(delegate + { + action(); + }); + }; + + // Sets ImageSharp Jpeg Encoder Quality. + SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() + { + Quality = 100, + }); + + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); + + // Now load the configuration as the other subsystems are now registered + ConfigurationPath = File.Exists(localConfigurationPath) + ? localConfigurationPath + : File.Exists(appDataConfigurationPath) + ? appDataConfigurationPath + : null; + + if (ConfigurationPath == null) + { + // No configuration, we load the default values and save it to disk + ConfigurationPath = appDataConfigurationPath; + + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); + } + else + { + if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) + { + ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); + } + else + { + ConfigurationState.Instance.LoadDefault(); + + Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); + } + } + + // Check if graphics backend was overridden. + if (CommandLineState.OverrideGraphicsBackend != null) + { + if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; + } + else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan; + } + } + + // Check if HideCursor was overridden. + if (CommandLineState.OverrideHideCursor is not null) + { + ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch + { + "never" => HideCursorMode.Never, + "onidle" => HideCursorMode.OnIdle, + "always" => HideCursorMode.Always, + _ => ConfigurationState.Instance.HideCursor.Value, + }; + } + + // Check if docked mode was overridden. + if (CommandLineState.OverrideDockedMode.HasValue) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; + } + + // Logging system information. + PrintSystemInfo(); + + // Enable OGL multithreading on the driver, when available. + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); + + // Initialize Gtk. + Application.Init(); + + // Check if keys exists. + bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); + bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")); + if (!hasSystemProdKeys && !hasCommonProdKeys) + { + UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); + } + + // Show the main window UI. + MainWindow mainWindow = new(); + mainWindow.Show(); + + if (OperatingSystem.IsLinux()) + { + int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; + + if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) + { + Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})"); + + if (LinuxHelper.PkExecPath is not null) + { + var buttonTexts = new Dictionary<int, string>() + { + { 0, "Yes, until the next restart" }, + { 1, "Yes, permanently" }, + { 2, "No" }, + }; + + ResponseType response = GtkDialog.CreateCustomDialog( + "Ryujinx - Low limit for memory mappings detected", + $"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?", + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.", + buttonTexts, + MessageType.Question); + + int rc; + + switch ((int)response) + { + case 0: + rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); + } + break; + case 1: + rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); + } + break; + } + } + else + { + GtkDialog.CreateWarningDialog( + "Max amount of memory mappings is lower than recommended.", + $"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." + + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.\n\n" + + "You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that."); + } + } + } + + if (CommandLineState.LaunchPathArg != null) + { + mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) + { + Updater.BeginParse(mainWindow, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + + Application.Run(); + } + + private static void PrintSystemInfo() + { + Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); + SystemInfo.Gather().Print(); + + var enabledLogs = Logger.GetEnabledLevels(); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}"); + + if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); + } + } + + private static void ProcessUnhandledException(Exception ex, bool isTerminating) + { + string message = $"Unhandled exception caught: {ex}"; + + Logger.Error?.PrintMsg(LogClass.Application, message); + + if (Logger.Error == null) + { + Logger.Notice.PrintMsg(LogClass.Application, message); + } + + if (isTerminating) + { + Exit(); + } + } + + public static void Exit() + { + DiscordIntegrationModule.Exit(); + + Logger.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj new file mode 100644 index 00000000..68bf9898 --- /dev/null +++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj @@ -0,0 +1,104 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> + <OutputType>Exe</OutputType> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <Version>1.0.0-dirty</Version> + <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> + <!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. --> + <SkipGtkInstall>true</SkipGtkInstall> + <TieredPGO>true</TieredPGO> + </PropertyGroup> + + <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''"> + <PublishSingleFile>true</PublishSingleFile> + <TrimmerSingleWarn>false</TrimmerSingleWarn> + <PublishTrimmed>true</PublishTrimmed> + <TrimMode>partial</TrimMode> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Ryujinx.GtkSharp" /> + <PackageReference Include="GtkSharp.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> + <PackageReference Include="GtkSharp.Dependencies.osx" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" /> + <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> + <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> + <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" /> + <PackageReference Include="OpenTK.Core" /> + <PackageReference Include="OpenTK.Graphics" /> + <PackageReference Include="SPB" /> + <PackageReference Include="SharpZipLib" /> + <PackageReference Include="SixLabors.ImageSharp" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> + <ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" /> + <ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" /> + <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" /> + <ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" /> + <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> + <ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" /> + <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" /> + <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> + <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> + <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" /> + <ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" /> + </ItemGroup> + + <ItemGroup> + <Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + <TargetPath>alsoft.ini</TargetPath> + </Content> + <Content Include="..\..\distribution\legal\THIRDPARTY.md"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + <TargetPath>THIRDPARTY.md</TargetPath> + </Content> + <Content Include="..\..\LICENSE.txt"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + <TargetPath>LICENSE.txt</TargetPath> + </Content> + </ItemGroup> + + <ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'"> + <Content Include="..\..\distribution\linux\Ryujinx.sh"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Include="..\..\distribution\linux\mime\Ryujinx.xml"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + <TargetPath>mime\Ryujinx.xml</TargetPath> + </Content> + </ItemGroup> + + <!-- Due to .net core 3.1 embedded resource loading --> + <PropertyGroup> + <EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention> + <ApplicationIcon>Ryujinx.ico</ApplicationIcon> + </PropertyGroup> + + <ItemGroup> + <None Remove="UI\MainWindow.glade" /> + <None Remove="UI\Widgets\ProfileDialog.glade" /> + <None Remove="UI\Windows\CheatWindow.glade" /> + <None Remove="UI\Windows\ControllerWindow.glade" /> + <None Remove="UI\Windows\DlcWindow.glade" /> + <None Remove="UI\Windows\SettingsWindow.glade" /> + <None Remove="UI\Windows\TitleUpdateWindow.glade" /> + <None Remove="Modules\Updater\UpdateDialog.glade" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="UI\MainWindow.glade" /> + <EmbeddedResource Include="UI\Widgets\ProfileDialog.glade" /> + <EmbeddedResource Include="UI\Windows\CheatWindow.glade" /> + <EmbeddedResource Include="UI\Windows\ControllerWindow.glade" /> + <EmbeddedResource Include="UI\Windows\DlcWindow.glade" /> + <EmbeddedResource Include="UI\Windows\SettingsWindow.glade" /> + <EmbeddedResource Include="UI\Windows\TitleUpdateWindow.glade" /> + <EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" /> + </ItemGroup> + +</Project> diff --git a/src/Ryujinx.Gtk3/Ryujinx.ico b/src/Ryujinx.Gtk3/Ryujinx.ico new file mode 100644 index 00000000..edf1b93f Binary files /dev/null and b/src/Ryujinx.Gtk3/Ryujinx.ico differ diff --git a/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs new file mode 100644 index 00000000..cb8103ca --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs @@ -0,0 +1,31 @@ +using Gtk; +using Ryujinx.UI.Common.Configuration; +using System.Reflection; + +namespace Ryujinx.UI.Applet +{ + internal class ErrorAppletDialog : MessageDialog + { + public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); + + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton("OK", 0); + } + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs new file mode 100644 index 00000000..0e560b78 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs @@ -0,0 +1,108 @@ +using Gtk; +using Ryujinx.HLE.UI; +using Ryujinx.Input.GTK3; +using Ryujinx.UI.Widgets; +using System.Threading; + +namespace Ryujinx.UI.Applet +{ + /// <summary> + /// Class that forwards key events to a GTK Entry so they can be processed into text. + /// </summary> + internal class GtkDynamicTextInputHandler : IDynamicTextInputHandler + { + private readonly Window _parent; + private readonly OffscreenWindow _inputToTextWindow = new(); + private readonly RawInputToTextEntry _inputToTextEntry = new(); + + private bool _canProcessInput; + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public GtkDynamicTextInputHandler(Window parent) + { + _parent = parent; + _parent.KeyPressEvent += HandleKeyPressEvent; + _parent.KeyReleaseEvent += HandleKeyReleaseEvent; + + _inputToTextWindow.Add(_inputToTextEntry); + + _inputToTextEntry.TruncateMultiline = true; + + // Start with input processing turned off so the text box won't accumulate text + // if the user is playing on the keyboard. + _canProcessInput = false; + } + + [GLib.ConnectBefore()] + private void HandleKeyPressEvent(object o, KeyPressEventArgs args) + { + var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + _inputToTextEntry.SendKeyPressEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + [GLib.ConnectBefore()] + private void HandleKeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + // TODO (caian): This solution may have problems if the pause is sent after a key press + // and before a key release. But for now GTK Entry does not seem to use release events. + _inputToTextEntry.SendKeyReleaseEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + public void SetText(string text, int cursorBegin) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.Position = cursorBegin; + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.SelectRegion(cursorBegin, cursorEnd); + } + + public void Dispose() + { + _parent.KeyPressEvent -= HandleKeyPressEvent; + _parent.KeyReleaseEvent -= HandleKeyReleaseEvent; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs new file mode 100644 index 00000000..1d918d21 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs @@ -0,0 +1,200 @@ +using Gtk; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.UI; +using Ryujinx.UI.Widgets; +using System; +using System.Threading; + +namespace Ryujinx.UI.Applet +{ + internal class GtkHostUIHandler : IHostUIHandler + { + private readonly Window _parent; + + public IHostUITheme HostUITheme { get; } + + public GtkHostUIHandler(Window parent) + { + _parent = parent; + + HostUITheme = new GtkHostUITheme(parent); + } + + public bool DisplayMessageDialog(ControllerAppletUIArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string message = $"Application requests <b>{playerCount}</b> player(s) with:\n\n" + + $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n" + + $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n" + + (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "") + + "<i>Please reconfigure Input now and then press OK.</i>"; + + return DisplayMessageDialog("Controller Applet", message); + } + + public bool DisplayMessageDialog(string title, string message) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + + Application.Invoke(delegate + { + MessageDialog msgDialog = null; + + try + { + msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) + { + Title = title, + Text = message, + UseMarkup = true, + }; + + msgDialog.SetDefaultSize(400, 0); + + msgDialog.Response += (object o, ResponseArgs args) => + { + if (args.ResponseId == ResponseType.Ok) + { + okPressed = true; + } + + dialogCloseEvent.Set(); + msgDialog?.Dispose(); + }; + + msgDialog.Show(); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog($"Error displaying Message Dialog: {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 ?? ""; + + Application.Invoke(delegate + { + try + { + var swkbdDialog = new SwkbdAppletDialog(_parent) + { + Title = "Software Keyboard", + Text = args.HeaderText, + SecondaryText = args.SubtitleText, + }; + + swkbdDialog.InputEntry.Text = inputText; + swkbdDialog.InputEntry.PlaceholderText = args.GuideText; + swkbdDialog.OkButton.Label = args.SubmitText; + + swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + swkbdDialog.SetInputValidation(args.KeyboardMode); + + if (swkbdDialog.Run() == (int)ResponseType.Ok) + { + inputText = swkbdDialog.InputEntry.Text; + okPressed = true; + } + + swkbdDialog.Dispose(); + } + catch (Exception ex) + { + error = true; + + GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}"); + } + finally + { + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + userText = error ? null : inputText; + + return error || okPressed; + } + + public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value) + { + device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); + ((MainWindow)_parent).RendererWidget?.Exit(); + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool showDetails = false; + + Application.Invoke(delegate + { + try + { + ErrorAppletDialog msgDialog = new(_parent, DialogFlags.DestroyWithParent, MessageType.Error, buttons) + { + Title = title, + Text = message, + UseMarkup = true, + WindowPosition = WindowPosition.CenterAlways, + }; + + msgDialog.SetDefaultSize(400, 0); + + msgDialog.Response += (object o, ResponseArgs args) => + { + if (buttons != null) + { + if (buttons.Length > 1) + { + if (args.ResponseId != (ResponseType)(buttons.Length - 1)) + { + showDetails = true; + } + } + } + + dialogCloseEvent.Set(); + msgDialog?.Dispose(); + }; + + msgDialog.Show(); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog($"Error displaying ErrorApplet Dialog: {ex}"); + + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + return showDetails; + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new GtkDynamicTextInputHandler(_parent); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs new file mode 100644 index 00000000..52d1123b --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs @@ -0,0 +1,90 @@ +using Gtk; +using Ryujinx.HLE.UI; +using System.Diagnostics; + +namespace Ryujinx.UI.Applet +{ + internal class GtkHostUITheme : IHostUITheme + { + private const int RenderSurfaceWidth = 32; + private const int RenderSurfaceHeight = 32; + + public string FontFamily { get; private set; } + + public ThemeColor DefaultBackgroundColor { get; } + public ThemeColor DefaultForegroundColor { get; } + public ThemeColor DefaultBorderColor { get; } + public ThemeColor SelectionBackgroundColor { get; } + public ThemeColor SelectionForegroundColor { get; } + + public GtkHostUITheme(Window parent) + { + Entry entry = new(); + entry.SetStateFlags(StateFlags.Selected, true); + + // Get the font and some colors directly from GTK. + FontFamily = entry.PangoContext.FontDescription.Family; + + // Get foreground colors from the style context. + + var defaultForegroundColor = entry.StyleContext.GetColor(StateFlags.Normal); + var selectedForegroundColor = entry.StyleContext.GetColor(StateFlags.Selected); + + DefaultForegroundColor = new ThemeColor((float)defaultForegroundColor.Alpha, (float)defaultForegroundColor.Red, (float)defaultForegroundColor.Green, (float)defaultForegroundColor.Blue); + SelectionForegroundColor = new ThemeColor((float)selectedForegroundColor.Alpha, (float)selectedForegroundColor.Red, (float)selectedForegroundColor.Green, (float)selectedForegroundColor.Blue); + + ListBoxRow row = new(); + row.SetStateFlags(StateFlags.Selected, true); + + // Request the main thread to render some UI elements to an image to get an approximation for the color. + // NOTE (caian): This will only take the color of the top-left corner of the background, which may be incorrect + // if someone provides a custom style with a gradient or image. + + using (var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, RenderSurfaceWidth, RenderSurfaceHeight)) + using (var context = new Cairo.Context(surface)) + { + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // The background color must be from the main Window because entry uses a different color. + parent.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBackgroundColor = ToThemeColor(surface.Data); + + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // Use the background color of the list box row when selected as the text box frame color because they are the + // same in the default theme. + row.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBorderColor = ToThemeColor(surface.Data); + } + + // Use the border color as the text selection color. + SelectionBackgroundColor = DefaultBorderColor; + } + + private static ThemeColor ToThemeColor(byte[] data) + { + Debug.Assert(data.Length == 4 * RenderSurfaceWidth * RenderSurfaceHeight); + + // Take the center-bottom pixel of the surface. + int position = 4 * (RenderSurfaceWidth * (RenderSurfaceHeight - 1) + RenderSurfaceWidth / 2); + + if (position + 4 > data.Length) + { + return new ThemeColor(1, 0, 0, 0); + } + + float a = data[position + 3] / 255.0f; + float r = data[position + 2] / 255.0f; + float g = data[position + 1] / 255.0f; + float b = data[position + 0] / 255.0f; + + return new ThemeColor(a, r, g, b); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs new file mode 100644 index 00000000..8045da91 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs @@ -0,0 +1,127 @@ +using Gtk; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using System; +using System.Linq; + +namespace Ryujinx.UI.Applet +{ + public class SwkbdAppletDialog : MessageDialog + { + private int _inputMin; + private int _inputMax; +#pragma warning disable IDE0052 // Remove unread private member + private KeyboardMode _mode; +#pragma warning restore IDE0052 + + private string _validationInfoText = ""; + + private Predicate<int> _checkLength = _ => true; + private Predicate<string> _checkInput = _ => true; + + private readonly Label _validationInfo; + + public Entry InputEntry { get; } + public Button OkButton { get; } + public Button CancelButton { get; } + + public SwkbdAppletDialog(Window parent) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null) + { + SetDefaultSize(300, 0); + + _validationInfo = new Label() + { + Visible = false, + }; + + InputEntry = new Entry() + { + Visible = true, + }; + + InputEntry.Activated += OnInputActivated; + InputEntry.Changed += OnInputChanged; + + OkButton = (Button)AddButton("OK", ResponseType.Ok); + CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel); + + ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0); + ((Box)MessageArea).PackEnd(InputEntry, true, true, 4); + } + + private void ApplyValidationInfo() + { + _validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText); + _validationInfo.Markup = _validationInfoText; + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + _validationInfo.Visible = false; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + _validationInfo.Visible = false; + + _checkLength = _ => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + _validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> "; + + _checkLength = length => _inputMin <= length; + } + else + { + _validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> "; + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + ApplyValidationInfo(); + OnInputChanged(this, EventArgs.Empty); + } + + public void SetInputValidation(KeyboardMode mode) + { + _mode = mode; + + switch (mode) + { + case KeyboardMode.Numeric: + _validationInfoText += "<i>Must be 0-9 or '.' only.</i>"; + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); + break; + case KeyboardMode.Alphabet: + _validationInfoText += "<i>Must be non CJK-characters only.</i>"; + _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); + break; + case KeyboardMode.ASCII: + _validationInfoText += "<i>Must be ASCII text only.</i>"; + _checkInput = text => text.All(char.IsAscii); + break; + default: + _checkInput = _ => true; + break; + } + + ApplyValidationInfo(); + OnInputChanged(this, EventArgs.Empty); + } + + private void OnInputActivated(object sender, EventArgs e) + { + if (OkButton.IsSensitive) + { + Respond(ResponseType.Ok); + } + } + + private void OnInputChanged(object sender, EventArgs e) + { + OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs new file mode 100644 index 00000000..c2c32d3a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs @@ -0,0 +1,135 @@ +using Gdk; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Helper +{ + public delegate void UpdateBoundsCallbackDelegate(Window window); + + [SupportedOSPlatform("macos")] + static partial class MetalHelper + { + private const string LibObjCImport = "/usr/lib/libobjc.A.dylib"; + + private readonly struct Selector + { + public readonly IntPtr NativePtr; + + public unsafe Selector(string value) + { + int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); + byte* data = stackalloc byte[size]; + + fixed (char* pValue = value) + { + System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); + } + + NativePtr = sel_registerName(data); + } + + public static implicit operator Selector(string value) => new(value); + } + + private static unsafe IntPtr GetClass(string value) + { + int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); + byte* data = stackalloc byte[size]; + + fixed (char* pValue = value) + { + System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); + } + + return objc_getClass(data); + } + + private struct NsPoint + { + public double X; + public double Y; + + public NsPoint(double x, double y) + { + X = x; + Y = y; + } + } + + private struct NsRect + { + public NsPoint Pos; + public NsPoint Size; + + public NsRect(double x, double y, double width, double height) + { + Pos = new NsPoint(x, y); + Size = new NsPoint(width, height); + } + } + + public static IntPtr GetMetalLayer(Display display, Window window, out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds) + { + nsView = gdk_quartz_window_get_nsview(window.Handle); + + // Create a new CAMetalLayer. + IntPtr layerClass = GetClass("CAMetalLayer"); + IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc"); + objc_msgSend(metalLayer, "init"); + + // Create a child NSView to render into. + IntPtr nsViewClass = GetClass("NSView"); + IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc"); + objc_msgSend(child, "init", new NsRect()); + + // Add it as a child. + objc_msgSend(nsView, "addSubview:", child); + + // Make its renderer our metal layer. + objc_msgSend(child, "setWantsLayer:", (byte)1); + objc_msgSend(child, "setLayer:", metalLayer); + objc_msgSend(metalLayer, "setContentsScale:", (double)display.GetMonitorAtWindow(window).ScaleFactor); + + // Set the frame position/location. + updateBounds = (Window window) => + { + window.GetPosition(out int x, out int y); + int width = window.Width; + int height = window.Height; + objc_msgSend(child, "setFrame:", new NsRect(x, y, width, height)); + }; + + updateBounds(window); + + return metalLayer; + } + + [LibraryImport(LibObjCImport)] + private static unsafe partial IntPtr sel_registerName(byte* data); + + [LibraryImport(LibObjCImport)] + private static unsafe partial IntPtr objc_getClass(byte* data); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, NsRect point); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value); + + [LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")] + private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport("libgdk-3.0.dylib")] + private static partial IntPtr gdk_quartz_window_get_nsview(IntPtr gdkWindow); + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs new file mode 100644 index 00000000..3e3fbeaa --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs @@ -0,0 +1,33 @@ +using Gtk; +using Ryujinx.UI.Common.Helper; +using System; + +namespace Ryujinx.UI.Helper +{ + static class SortHelper + { + public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + TimeSpan aTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(a, 5).ToString()); + TimeSpan bTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(b, 5).ToString()); + + return TimeSpan.Compare(aTimeSpan, bTimeSpan); + } + + public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + DateTime aDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(a, 6).ToString()); + DateTime bDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(b, 6).ToString()); + + return DateTime.Compare(aDateTime, bDateTime); + } + + public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) + { + long aSize = ValueFormatUtils.ParseFileSize(model.GetValue(a, 8).ToString()); + long bSize = ValueFormatUtils.ParseFileSize(model.GetValue(b, 8).ToString()); + + return aSize.CompareTo(bSize); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs new file mode 100644 index 00000000..e1fed1c4 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs @@ -0,0 +1,36 @@ +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System.IO; + +namespace Ryujinx.UI.Helper +{ + static class ThemeHelper + { + public static void ApplyTheme() + { + if (!ConfigurationState.Instance.UI.EnableCustomTheme) + { + return; + } + + if (File.Exists(ConfigurationState.Instance.UI.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.UI.CustomThemePath) == ".css")) + { + CssProvider cssProvider = new(); + + cssProvider.LoadFromPath(ConfigurationState.Instance.UI.CustomThemePath); + + StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.UI.CustomThemePath}\"."); + + ConfigurationState.Instance.UI.CustomThemePath.Value = ""; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = false; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs new file mode 100644 index 00000000..d1ca6ce6 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -0,0 +1,1941 @@ +using Gtk; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Cpu; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input.GTK3; +using Ryujinx.Input.HLE; +using Ryujinx.Input.SDL2; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Applet; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; +using Ryujinx.UI.Windows; +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using GUI = Gtk.Builder.ObjectAttribute; +using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; + +namespace Ryujinx.UI +{ + public class MainWindow : Window + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; + private readonly AccountManager _accountManager; + private readonly LibHacHorizonManager _libHacHorizonManager; + + private UserChannelPersistence _userChannelPersistence; + + private HLE.Switch _emulationContext; + + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private readonly ApplicationLibrary _applicationLibrary; + private readonly GtkHostUIHandler _uiHandler; + private readonly AutoResetEvent _deviceExitStatus; + private readonly ListStore _tableStore; + + private bool _updatingGameTable; + private bool _gameLoaded; + private bool _ending; + + private string _currentEmulatedGamePath = null; + + private string _lastScannedAmiiboId = ""; + private bool _lastScannedAmiiboShowAll = false; + + public RendererWidgetBase RendererWidget; + public InputManager InputManager; + + public bool IsFocused; + +#pragma warning disable CS0169, CS0649, IDE0044, IDE0051 // Field is never assigned to, Add readonly modifier, Remove unused private member + + [GUI] public MenuItem ExitMenuItem; + [GUI] public MenuItem UpdateMenuItem; + [GUI] MenuBar _menuBar; + [GUI] Box _footerBox; + [GUI] Box _statusBar; + [GUI] MenuItem _optionMenu; + [GUI] MenuItem _manageUserProfiles; + [GUI] MenuItem _fileMenu; + [GUI] MenuItem _loadApplicationFile; + [GUI] MenuItem _loadApplicationFolder; + [GUI] MenuItem _appletMenu; + [GUI] MenuItem _actionMenu; + [GUI] MenuItem _pauseEmulation; + [GUI] MenuItem _resumeEmulation; + [GUI] MenuItem _stopEmulation; + [GUI] MenuItem _simulateWakeUpMessage; + [GUI] MenuItem _scanAmiibo; + [GUI] MenuItem _takeScreenshot; + [GUI] MenuItem _hideUI; + [GUI] MenuItem _fullScreen; + [GUI] CheckMenuItem _startFullScreen; + [GUI] CheckMenuItem _showConsole; + [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstallDirectory; + [GUI] MenuItem _firmwareInstallFile; + [GUI] MenuItem _fileTypesSubMenu; + [GUI] Label _fifoStatus; + [GUI] CheckMenuItem _iconToggle; + [GUI] CheckMenuItem _developerToggle; + [GUI] CheckMenuItem _appToggle; + [GUI] CheckMenuItem _timePlayedToggle; + [GUI] CheckMenuItem _versionToggle; + [GUI] CheckMenuItem _lastPlayedToggle; + [GUI] CheckMenuItem _fileExtToggle; + [GUI] CheckMenuItem _pathToggle; + [GUI] CheckMenuItem _fileSizeToggle; + [GUI] CheckMenuItem _nspShown; + [GUI] CheckMenuItem _pfs0Shown; + [GUI] CheckMenuItem _xciShown; + [GUI] CheckMenuItem _ncaShown; + [GUI] CheckMenuItem _nroShown; + [GUI] CheckMenuItem _nsoShown; + [GUI] Label _gpuBackend; + [GUI] Label _dockedMode; + [GUI] Label _aspectRatio; + [GUI] Label _gameStatus; + [GUI] TreeView _gameTable; + [GUI] TreeSelection _gameTableSelection; + [GUI] ScrolledWindow _gameTableWindow; + [GUI] Label _gpuName; + [GUI] Label _progressLabel; + [GUI] Label _firmwareVersionLabel; + [GUI] Gtk.ProgressBar _progressBar; + [GUI] Box _viewBox; + [GUI] Label _vSyncStatus; + [GUI] Label _volumeStatus; + [GUI] Box _listStatusBox; + [GUI] Label _loadingStatusLabel; + [GUI] Gtk.ProgressBar _loadingStatusBar; + +#pragma warning restore CS0649, IDE0044, CS0169, IDE0051 + + public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { } + + private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) + { + builder.Autoconnect(this); + + // Apply custom theme if needed. + ThemeHelper.ApplyTheme(); + + SetWindowSizePosition(); + + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Title = $"Ryujinx {Program.Version}"; + + // Hide emulation context status bar. + _statusBar.Hide(); + + // Instantiate HLE objects. + _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _libHacHorizonManager = new LibHacHorizonManager(); + + _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); + _libHacHorizonManager.InitializeArpServer(); + _libHacHorizonManager.InitializeBcatServer(); + _libHacHorizonManager.InitializeSystemClients(); + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient); + + _contentManager = new ContentManager(_virtualFileSystem); + _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile); + _userChannelPersistence = new UserChannelPersistence(); + + // Instantiate GUI objects. + _applicationLibrary = new ApplicationLibrary(_virtualFileSystem); + _uiHandler = new GtkHostUIHandler(this); + _deviceExitStatus = new AutoResetEvent(false); + + WindowStateEvent += WindowStateEvent_Changed; + DeleteEvent += Window_Close; + FocusInEvent += MainWindow_FocusInEvent; + FocusOutEvent += MainWindow_FocusOutEvent; + + _applicationLibrary.ApplicationAdded += Application_Added; + _applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; + + _fileMenu.StateChanged += FileMenu_StateChanged; + _actionMenu.StateChanged += ActionMenu_StateChanged; + _optionMenu.StateChanged += OptionMenu_StateChanged; + + _gameTable.ButtonReleaseEvent += Row_Clicked; + _fullScreen.Activated += FullScreen_Toggled; + + RendererWidgetBase.StatusUpdatedEvent += Update_StatusBar; + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId; + + if (ConfigurationState.Instance.UI.StartFullscreen) + { + _startFullScreen.Active = true; + } + + _showConsole.Active = ConfigurationState.Instance.UI.ShowConsole.Value; + _showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported; + + _actionMenu.Sensitive = false; + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = false; + + _nspShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value; + _pfs0Shown.Active = ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value; + _xciShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value; + _ncaShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value; + _nroShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value; + _nsoShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value; + + _nspShown.Toggled += NSP_Shown_Toggled; + _pfs0Shown.Toggled += PFS0_Shown_Toggled; + _xciShown.Toggled += XCI_Shown_Toggled; + _ncaShown.Toggled += NCA_Shown_Toggled; + _nroShown.Toggled += NRO_Shown_Toggled; + _nsoShown.Toggled += NSO_Shown_Toggled; + + _fileTypesSubMenu.Visible = FileAssociationHelper.IsTypeAssociationSupported; + + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) + { + _favToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) + { + _iconToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) + { + _appToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) + { + _developerToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) + { + _versionToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) + { + _timePlayedToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) + { + _lastPlayedToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) + { + _fileExtToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) + { + _fileSizeToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) + { + _pathToggle.Active = true; + } + + _favToggle.Toggled += Fav_Toggled; + _iconToggle.Toggled += Icon_Toggled; + _appToggle.Toggled += App_Toggled; + _developerToggle.Toggled += Developer_Toggled; + _versionToggle.Toggled += Version_Toggled; + _timePlayedToggle.Toggled += TimePlayed_Toggled; + _lastPlayedToggle.Toggled += LastPlayed_Toggled; + _fileExtToggle.Toggled += FileExt_Toggled; + _fileSizeToggle.Toggled += FileSize_Toggled; + _pathToggle.Toggled += Path_Toggled; + + _gameTable.Model = _tableStore = new ListStore( + typeof(bool), + typeof(Gdk.Pixbuf), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(BlitStruct<ApplicationControlProperty>)); + + _tableStore.SetSortFunc(5, SortHelper.TimePlayedSort); + _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort); + _tableStore.SetSortFunc(8, SortHelper.FileSizeSort); + + int columnId = ConfigurationState.Instance.UI.ColumnSort.SortColumnId; + bool ascending = ConfigurationState.Instance.UI.ColumnSort.SortAscending; + + _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending); + + _gameTable.EnableSearch = true; + _gameTable.SearchColumn = 2; + _gameTable.SearchEqualFunc = (model, col, key, iter) => !((string)model.GetValue(iter, col)).Contains(key, StringComparison.InvariantCultureIgnoreCase); + + _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); + + UpdateColumns(); + UpdateGameTable(); + + ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => + { + if (args.OldValue != args.NewValue) + { + UpdateGameTable(); + } + }; + + Task.Run(RefreshFirmwareLabel); + + InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver()); + } + + private void UpdateMultiplayerLanInterfaceId(object sender, ReactiveEventArgs<string> args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.MultiplayerLanInterfaceId = args.NewValue; + } + } + + private void UpdateMultiplayerMode(object sender, ReactiveEventArgs<MultiplayerMode> args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.MultiplayerMode = args.NewValue; + } + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e) + { + _emulationContext?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e) + { + _emulationContext?.SetVolume(e.NewValue); + } + + private void WindowStateEvent_Changed(object o, WindowStateEventArgs args) + { + _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen"; + } + + private void MainWindow_FocusOutEvent(object o, FocusOutEventArgs args) + { + IsFocused = false; + } + + private void MainWindow_FocusInEvent(object o, FocusInEventArgs args) + { + IsFocused = true; + } + + private void UpdateColumns() + { + foreach (TreeViewColumn column in _gameTable.Columns) + { + _gameTable.RemoveColumn(column); + } + + CellRendererToggle favToggle = new(); + favToggle.Toggled += FavToggle_Toggled; + + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) + { + _gameTable.AppendColumn("Fav", favToggle, "active", 0); + } + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) + { + _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); + } + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) + { + _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); + } + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) + { + _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); + } + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) + { + _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); + } + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) + { + _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); + } + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) + { + _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); + } + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) + { + _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); + } + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) + { + _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); + } + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) + { + _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); + } + + foreach (TreeViewColumn column in _gameTable.Columns) + { + switch (column.Title) + { + case "Fav": + column.SortColumnId = 0; + column.Clicked += Column_Clicked; + break; + case "Application": + column.SortColumnId = 2; + column.Clicked += Column_Clicked; + break; + case "Developer": + column.SortColumnId = 3; + column.Clicked += Column_Clicked; + break; + case "Version": + column.SortColumnId = 4; + column.Clicked += Column_Clicked; + break; + case "Time Played": + column.SortColumnId = 5; + column.Clicked += Column_Clicked; + break; + case "Last Played": + column.SortColumnId = 6; + column.Clicked += Column_Clicked; + break; + case "File Ext": + column.SortColumnId = 7; + column.Clicked += Column_Clicked; + break; + case "File Size": + column.SortColumnId = 8; + column.Clicked += Column_Clicked; + break; + case "Path": + column.SortColumnId = 9; + column.Clicked += Column_Clicked; + break; + } + } + } + + protected override void OnDestroyed() + { + InputManager.Dispose(); + } + + private void InitializeSwitchInstance() + { + _virtualFileSystem.ReloadKeySet(); + + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) + { + string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; + renderer = new Graphics.Vulkan.VulkanRenderer(Vk.GetApi(), CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); + } + else + { + renderer = new Graphics.OpenGL.OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {threadedGAL}"); + + IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); + + if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) + { + if (SDL2HardwareDeviceDriver.IsSupported) + { + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) + { + if (SoundIoHardwareDeviceDriver.IsSupported) + { + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) + { + if (OpenALHardwareDeviceDriver.IsSupported) + { + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value + ? HLE.MemoryConfiguration.MemoryConfiguration6GiB + : HLE.MemoryConfiguration.MemoryConfiguration4GiB; + + IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + + HLE.HLEConfiguration configuration = new(_virtualFileSystem, + _libHacHorizonManager, + _contentManager, + _accountManager, + _userChannelPersistence, + renderer, + deviceDriver, + memoryConfiguration, + _uiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + fsIntegrityCheckLevel, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); + + _emulationContext = new HLE.Switch(configuration); + } + + private SurfaceKHR CreateVulkanSurface(Instance instance, Vk vk) + { + return new SurfaceKHR((ulong)((VulkanRenderer)RendererWidget).CreateWindowSurface(instance.Handle)); + } + + private void SetupProgressUIHandlers() + { + if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) + { + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; + } + + _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; + _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; + } + + private void ProgressHandler<T>(T state, int current, int total) where T : Enum + { + bool visible; + string label; + + switch (state) + { + case LoadState ptcState: + visible = ptcState != LoadState.Loaded; + label = $"PTC : {current}/{total}"; + break; + case ShaderCacheLoadingState shaderCacheState: + visible = shaderCacheState != ShaderCacheLoadingState.Loaded; + label = $"Shaders : {current}/{total}"; + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } + + Application.Invoke(delegate + { + _loadingStatusLabel.Text = label; + _loadingStatusBar.Fraction = total > 0 ? (double)current / total : 0; + _loadingStatusBar.Visible = visible; + _loadingStatusLabel.Visible = visible; + }); + } + + public void UpdateGameTable() + { + if (_updatingGameTable || _gameLoaded) + { + return; + } + + _updatingGameTable = true; + + _tableStore.Clear(); + + Thread applicationLibraryThread = new(() => + { + _applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + + _updatingGameTable = false; + }) + { + Name = "GUI.ApplicationLibraryThread", + IsBackground = true, + }; + applicationLibraryThread.Start(); + } + + [Conditional("RELEASE")] + public void PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableTrace.Value) + { + MessageDialog debugWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have trace logging enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + }; + + if (debugWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Logger.EnableTrace.Value = false; + SaveConfig(); + } + + debugWarningDialog.Dispose(); + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + MessageDialog shadersDumpWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have shader dumping enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + }; + + if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + SaveConfig(); + } + + shadersDumpWarningDialog.Dispose(); + } + } + + private bool LoadApplication(string path, bool isFirmwareTitle) + { + SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) + { + if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) + { + string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; + + ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); + + if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + + // Tell the user that we installed a firmware for them. + + firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + RefreshFirmwareLabel(); + + message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; + + GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); + } + else + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + return _emulationContext.LoadNca(path); + } + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + return _emulationContext.LoadCart(path, romFsFiles[0]); + } + + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + return _emulationContext.LoadCart(path); + } + + if (File.Exists(path)) + { + switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + return _emulationContext.LoadXci(path); + case ".nca": + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + return _emulationContext.LoadNca(path); + case ".nsp": + case ".pfs0": + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + return _emulationContext.LoadNsp(path); + default: + Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); + try + { + return _emulationContext.LoadProgram(path); + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + return false; + } + } + } + + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + return false; + } + + public void RunApplication(string path, bool startFullscreen = false) + { + if (_gameLoaded) + { + GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); + } + else + { + PerformanceCheck(); + + Logger.RestartTime(); + + RendererWidget = CreateRendererWidget(); + + SwitchToRenderWidget(startFullscreen); + + InitializeSwitchInstance(); + + UpdateGraphicsConfig(); + + bool isFirmwareTitle = false; + + if (path.StartsWith("@SystemContent")) + { + path = VirtualFileSystem.SwitchPathToSystemPath(path); + + isFirmwareTitle = true; + } + + if (!LoadApplication(path, isFirmwareTitle)) + { + _emulationContext.Dispose(); + SwitchToGameTable(); + + return; + } + + SetupProgressUIHandlers(); + + _currentEmulatedGamePath = path; + + _deviceExitStatus.Reset(); + + Thread windowThread = new(CreateGameWindow) + { + Name = "GUI.WindowThread", + }; + + windowThread.Start(); + + _gameLoaded = true; + _actionMenu.Sensitive = true; + UpdateMenuItem.Sensitive = false; + + _lastScannedAmiiboId = ""; + + _firmwareInstallFile.Sensitive = false; + _firmwareInstallDirectory.Sensitive = false; + + DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); + + ApplicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.UpdatePreGame(); + }); + } + } + + private RendererWidgetBase CreateRendererWidget() + { + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) + { + return new VulkanRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + } + else + { + return new OpenGLRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + } + } + + private void SwitchToRenderWidget(bool startFullscreen = false) + { + _viewBox.Remove(_gameTableWindow); + RendererWidget.Expand = true; + _viewBox.Child = RendererWidget; + + RendererWidget.ShowAll(); + EditFooterForGameRenderer(); + + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + ToggleExtraWidgets(false); + } + else if (startFullscreen || ConfigurationState.Instance.UI.StartFullscreen.Value) + { + FullScreen_Toggled(null, null); + } + } + + private void SwitchToGameTable() + { + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + ToggleExtraWidgets(true); + } + + RendererWidget.Exit(); + + if (RendererWidget.Window != Window && RendererWidget.Window != null) + { + RendererWidget.Window.Dispose(); + } + + RendererWidget.Dispose(); + + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + DisplaySleep.Restore(); + + _viewBox.Remove(RendererWidget); + _viewBox.Add(_gameTableWindow); + + _gameTableWindow.Expand = true; + + Window.Title = $"Ryujinx {Program.Version}"; + + _emulationContext = null; + _gameLoaded = false; + RendererWidget = null; + + DiscordIntegrationModule.SwitchToMainMenu(); + + RecreateFooterForMenu(); + + UpdateColumns(); + UpdateGameTable(); + + RefreshFirmwareLabel(); + HandleRelaunch(); + } + + private void CreateGameWindow() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + RendererWidget.Initialize(_emulationContext); + + RendererWidget.WaitEvent.WaitOne(); + + RendererWidget.Start(); + + _emulationContext.Dispose(); + _deviceExitStatus.Set(); + + // NOTE: Everything that is here will not be executed when you close the UI. + Application.Invoke(delegate + { + SwitchToGameTable(); + }); + } + + private void RecreateFooterForMenu() + { + _listStatusBox.Show(); + _statusBar.Hide(); + } + + private void EditFooterForGameRenderer() + { + _listStatusBox.Hide(); + _statusBar.Show(); + } + + public void ToggleExtraWidgets(bool show) + { + if (RendererWidget != null) + { + if (show) + { + _menuBar.ShowAll(); + _footerBox.Show(); + _statusBar.Show(); + } + else + { + _menuBar.Hide(); + _footerBox.Hide(); + } + } + } + + private void UpdateGameMetadata(string titleId) + { + if (_gameLoaded) + { + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.UpdatePostGame(); + }); + } + } + + public static void UpdateGraphicsConfig() + { + int resScale = ConfigurationState.Instance.Graphics.ResScale; + float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; + + Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale; + Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + Graphics.Gpu.GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; + Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; + } + + public void UpdateInternetAccess() + { + if (_gameLoaded) + { + _emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value; + } + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + private void End() + { + if (_ending) + { + return; + } + + _ending = true; + + if (_emulationContext != null) + { + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); + + if (RendererWidget != null) + { + // We tell the widget that we are exiting. + RendererWidget.Exit(); + + // Wait for the other thread to dispose the HLE context before exiting. + _deviceExitStatus.WaitOne(); + RendererWidget.Dispose(); + } + } + + Dispose(); + + Program.Exit(); + Application.Quit(); + } + + // + // Events + // + private void Application_Added(object sender, ApplicationAddedEventArgs args) + { + Application.Invoke(delegate + { + _tableStore.AppendValues( + args.AppData.Favorite, + new Gdk.Pixbuf(args.AppData.Icon, 75, 75), + $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}", + args.AppData.Developer, + args.AppData.Version, + args.AppData.TimePlayedString, + args.AppData.LastPlayedString, + args.AppData.FileExtension, + args.AppData.FileSizeString, + args.AppData.Path, + args.AppData.ControlHolder); + }); + } + + private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args) + { + Application.Invoke(delegate + { + _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; + float barValue = 0; + + if (args.NumAppsFound != 0) + { + barValue = (float)args.NumAppsLoaded / args.NumAppsFound; + } + + _progressBar.Fraction = barValue; + + // Reset the vertical scrollbar to the top when titles finish loading + if (args.NumAppsLoaded == args.NumAppsFound) + { + _gameTableWindow.Vadjustment.Value = 0; + } + }); + } + + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) + { + Application.Invoke(delegate + { + _gameStatus.Text = args.GameStatus; + _fifoStatus.Text = args.FifoStatus; + _gpuName.Text = args.GpuName; + _dockedMode.Text = args.DockedMode; + _aspectRatio.Text = args.AspectRatio; + _gpuBackend.Text = args.GpuBackend; + _volumeStatus.Text = GetVolumeLabelText(args.Volume); + + if (args.VSyncEnabled) + { + _vSyncStatus.Attributes = new Pango.AttrList(); + _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(11822, 60138, 51657)); + } + else + { + _vSyncStatus.Attributes = new Pango.AttrList(); + _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(ushort.MaxValue, 17733, 21588)); + } + }); + } + + private void FavToggle_Toggled(object sender, ToggledArgs args) + { + _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); + bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); + + _tableStore.SetValue(treeIter, 0, newToggleValue); + + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.Favorite = newToggleValue; + }); + } + + private void Column_Clicked(object sender, EventArgs args) + { + TreeViewColumn column = (TreeViewColumn)sender; + + ConfigurationState.Instance.UI.ColumnSort.SortColumnId.Value = column.SortColumnId; + ConfigurationState.Instance.UI.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending; + + SaveConfig(); + } + + private void Row_Activated(object sender, RowActivatedArgs args) + { + _gameTableSelection.GetSelected(out TreeIter treeIter); + + string path = (string)_tableStore.GetValue(treeIter, 9); + + RunApplication(path); + } + + private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) + { + _emulationContext.EnableDeviceVsync = !_emulationContext.EnableDeviceVsync; + + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {_emulationContext.EnableDeviceVsync}"); + } + + private void DockedMode_Clicked(object sender, ButtonReleaseEventArgs args) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + + private static string GetVolumeLabelText(float volume) + { + string icon = volume == 0 ? "🔇" : "🔊"; + + return $"{icon} {(int)(volume * 100)}%"; + } + + private void VolumeStatus_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (_emulationContext != null) + { + if (_emulationContext.IsAudioMuted()) + { + _emulationContext.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + _emulationContext.SetVolume(0); + } + } + } + + private void AspectRatio_Clicked(object sender, ButtonReleaseEventArgs args) + { + AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; + + ConfigurationState.Instance.Graphics.AspectRatio.Value = ((int)aspectRatio + 1) > Enum.GetNames<AspectRatio>().Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; + } + + private void Row_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3 /* Right Click */) + { + return; + } + + _gameTableSelection.GetSelected(out TreeIter treeIter); + + if (treeIter.UserData == IntPtr.Zero) + { + return; + } + + string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString(); + string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0]; + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); + + BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10); + + _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData); + } + + private void Load_Application_File(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Choose the file to open", this, FileChooserAction.Open, "Open", "Cancel"); + + FileFilter filter = new() + { + Name = "Switch Executables", + }; + filter.AddPattern("*.xci"); + filter.AddPattern("*.nsp"); + filter.AddPattern("*.pfs0"); + filter.AddPattern("*.nca"); + filter.AddPattern("*.nro"); + filter.AddPattern("*.nso"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + RunApplication(fileChooser.Filename); + } + } + + private void Load_Application_Folder(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Choose the folder to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + RunApplication(fileChooser.Filename); + } + } + + private void FileMenu_StateChanged(object o, StateChangedArgs args) + { + _appletMenu.Sensitive = _emulationContext == null && _contentManager.GetCurrentFirmwareVersion() != null && _contentManager.GetCurrentFirmwareVersion().Major > 3; + _loadApplicationFile.Sensitive = _emulationContext == null; + _loadApplicationFolder.Sensitive = _emulationContext == null; + } + + private void Load_Mii_Edit_Applet(object sender, EventArgs args) + { + string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + + RunApplication(contentPath); + } + + private void Open_Ryu_Folder(object sender, EventArgs args) + { + OpenHelper.OpenFolder(AppDataManager.BaseDirPath); + } + + private void OpenLogsFolder_Pressed(object sender, EventArgs args) + { + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) + { + OpenHelper.OpenFolder(logPath); + } + } + + private void Exit_Pressed(object sender, EventArgs args) + { + if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + SaveWindowSizePosition(); + End(); + } + } + + private void Window_Close(object sender, DeleteEventArgs args) + { + if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + SaveWindowSizePosition(); + End(); + } + else + { + args.RetVal = true; + } + } + + private void SetWindowSizePosition() + { + DefaultWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth; + DefaultHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight; + + Move(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); + + if (ConfigurationState.Instance.UI.WindowStartup.WindowMaximized) + { + Maximize(); + } + } + + private void SaveWindowSizePosition() + { + GetSize(out int windowWidth, out int windowHeight); + GetPosition(out int windowXPos, out int windowYPos); + + ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = IsMaximized; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = windowWidth; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = windowHeight; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = windowXPos; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = windowYPos; + + SaveConfig(); + } + + private void StopEmulation_Pressed(object sender, EventArgs args) + { + if (_emulationContext != null) + { + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); + } + + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = false; + UpdateMenuItem.Sensitive = true; + RendererWidget?.Exit(); + } + + private void PauseEmulation_Pressed(object sender, EventArgs args) + { + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = true; + _emulationContext.System.TogglePauseEmulation(true); + Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version, "Paused"); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + } + + private void ResumeEmulation_Pressed(object sender, EventArgs args) + { + _pauseEmulation.Sensitive = true; + _resumeEmulation.Sensitive = false; + _emulationContext.System.TogglePauseEmulation(false); + Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + } + + public void ActivatePauseMenu() + { + _pauseEmulation.Sensitive = true; + _resumeEmulation.Sensitive = false; + } + + public void TogglePause() + { + _pauseEmulation.Sensitive ^= true; + _resumeEmulation.Sensitive ^= true; + _emulationContext.System.TogglePauseEmulation(_resumeEmulation.Sensitive); + } + + private void Installer_File_Pressed(object o, EventArgs args) + { + FileChooserNative fileChooser = new("Choose the firmware file to open", this, FileChooserAction.Open, "Open", "Cancel"); + + FileFilter filter = new() + { + Name = "Switch Firmware Files", + }; + filter.AddPattern("*.zip"); + filter.AddPattern("*.xci"); + + fileChooser.AddFilter(filter); + + HandleInstallerDialog(fileChooser); + } + + private void Installer_Directory_Pressed(object o, EventArgs args) + { + FileChooserNative directoryChooser = new("Choose the firmware directory to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); + + HandleInstallerDialog(directoryChooser); + } + + private void HandleInstallerDialog(FileChooserNative fileChooser) + { + if (fileChooser.Run() == (int)ResponseType.Accept) + { + try + { + string filename = fileChooser.Filename; + + fileChooser.Dispose(); + + SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); + + if (firmwareVersion is null) + { + GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); + + return; + } + + string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; + + SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion(); + + string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; + + if (currentVersion != null) + { + dialogMessage += $"\n\nThis will replace the current system version {currentVersion.VersionString}. "; + } + + dialogMessage += "\n\nDo you want to continue?"; + + ResponseType responseInstallDialog = (ResponseType)GtkDialog.CreateConfirmationDialog(dialogTitle, dialogMessage).Run(); + + MessageDialog waitingDialog = GtkDialog.CreateWaitingDialog(dialogTitle, "Installing firmware..."); + + if (responseInstallDialog == ResponseType.Yes) + { + Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new(() => + { + Application.Invoke(delegate + { + waitingDialog.Run(); + + }); + + try + { + _contentManager.InstallFirmware(filename); + + Application.Invoke(delegate + { + waitingDialog.Dispose(); + + string message = $"System version {firmwareVersion.VersionString} successfully installed."; + + GtkDialog.CreateInfoDialog(dialogTitle, message); + Logger.Info?.Print(LogClass.Application, message); + + // Purge Applet Cache. + + DirectoryInfo miiEditorCacheFolder = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + + if (miiEditorCacheFolder.Exists) + { + miiEditorCacheFolder.Delete(true); + } + }); + } + catch (Exception ex) + { + Application.Invoke(delegate + { + waitingDialog.Dispose(); + + GtkDialog.CreateErrorDialog(ex.Message); + }); + } + finally + { + RefreshFirmwareLabel(); + } + }) + { + Name = "GUI.FirmwareInstallerThread", + }; + thread.Start(); + } + } + catch (MissingKeyException ex) + { + Logger.Error?.Print(LogClass.Application, ex.ToString()); + UserErrorDialog.CreateUserErrorDialog(UserError.FirmwareParsingFailed); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog(ex.Message); + } + } + else + { + fileChooser.Dispose(); + } + } + + private void RefreshFirmwareLabel() + { + SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion(); + + Application.Invoke(delegate + { + _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; + }); + } + + private void InstallFileTypes_Pressed(object sender, EventArgs e) + { + if (FileAssociationHelper.Install()) + { + GtkDialog.CreateInfoDialog("Install file types", "File types successfully installed!"); + } + else + { + GtkDialog.CreateErrorDialog("Failed to install file types."); + } + } + + private void UninstallFileTypes_Pressed(object sender, EventArgs e) + { + if (FileAssociationHelper.Uninstall()) + { + GtkDialog.CreateInfoDialog("Uninstall file types", "File types successfully uninstalled!"); + } + else + { + GtkDialog.CreateErrorDialog("Failed to uninstall file types."); + } + } + + private void HandleRelaunch() + { + if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) + { + _userChannelPersistence.ShouldRestart = false; + + RunApplication(_currentEmulatedGamePath); + } + else + { + // otherwise, clear state. + _userChannelPersistence = new UserChannelPersistence(); + _currentEmulatedGamePath = null; + _actionMenu.Sensitive = false; + _firmwareInstallFile.Sensitive = true; + _firmwareInstallDirectory.Sensitive = true; + } + } + + private void FullScreen_Toggled(object sender, EventArgs args) + { + if (!Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + Fullscreen(); + + ToggleExtraWidgets(false); + } + else + { + Unfullscreen(); + + ToggleExtraWidgets(true); + } + } + + private void StartFullScreen_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.StartFullscreen.Value = _startFullScreen.Active; + + SaveConfig(); + } + + private void ShowConsole_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShowConsole.Value = _showConsole.Active; + + SaveConfig(); + } + + private void OptionMenu_StateChanged(object o, StateChangedArgs args) + { + _manageUserProfiles.Sensitive = _emulationContext == null; + } + + private void Settings_Pressed(object sender, EventArgs args) + { + SettingsWindow settingsWindow = new(this, _virtualFileSystem, _contentManager); + + settingsWindow.SetSizeRequest((int)(settingsWindow.DefaultWidth * Program.WindowScaleFactor), (int)(settingsWindow.DefaultHeight * Program.WindowScaleFactor)); + settingsWindow.Show(); + } + + private void HideUI_Pressed(object sender, EventArgs args) + { + ToggleExtraWidgets(false); + } + + private void ManageCheats_Pressed(object sender, EventArgs args) + { + var window = new CheatWindow( + _virtualFileSystem, + _emulationContext.Processes.ActiveApplication.ProgramId, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties + .Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(), + _currentEmulatedGamePath); + + window.Destroyed += CheatWindow_Destroyed; + window.Show(); + } + + private void CheatWindow_Destroyed(object sender, EventArgs e) + { + _emulationContext.EnableCheats(); + (sender as CheatWindow).Destroyed -= CheatWindow_Destroyed; + } + + private void ManageUserProfiles_Pressed(object sender, EventArgs args) + { + UserProfilesManagerWindow userProfilesManagerWindow = new(_accountManager, _contentManager, _virtualFileSystem); + + userProfilesManagerWindow.SetSizeRequest((int)(userProfilesManagerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(userProfilesManagerWindow.DefaultHeight * Program.WindowScaleFactor)); + userProfilesManagerWindow.Show(); + } + + private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args) + { + _emulationContext?.System.SimulateWakeUpMessage(); + } + + private void ActionMenu_StateChanged(object o, StateChangedArgs args) + { + _scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _); + _takeScreenshot.Sensitive = _emulationContext != null; + } + + private void Scan_Amiibo(object sender, EventArgs args) + { + if (_emulationContext.System.SearchingForAmiibo(out int deviceId)) + { + AmiiboWindow amiiboWindow = new() + { + LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, + LastScannedAmiiboId = _lastScannedAmiiboId, + DeviceId = deviceId, + TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper(), + }; + + amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; + + amiiboWindow.Show(); + } + else + { + GtkDialog.CreateInfoDialog($"Amiibo", "The game is currently not ready to receive Amiibo scan data. Ensure that you have an Amiibo-compatible game open and ready to receive Amiibo scan data."); + } + } + + private void Take_Screenshot(object sender, EventArgs args) + { + if (_emulationContext != null && RendererWidget != null) + { + RendererWidget.ScreenshotRequested = true; + } + } + + private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args) + { + if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok) + { + _lastScannedAmiiboId = ((AmiiboWindow)sender).AmiiboId; + _lastScannedAmiiboShowAll = ((AmiiboWindow)sender).LastScannedAmiiboShowAll; + + _emulationContext.System.ScanAmiibo(((AmiiboWindow)sender).DeviceId, ((AmiiboWindow)sender).AmiiboId, ((AmiiboWindow)sender).UseRandomUuid); + } + } + + private void Update_Pressed(object sender, EventArgs args) + { + if (Updater.CanUpdate(true)) + { + Updater.BeginParse(this, true).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + } + + private void About_Pressed(object sender, EventArgs args) + { + AboutWindow aboutWindow = new(); + + aboutWindow.SetSizeRequest((int)(aboutWindow.DefaultWidth * Program.WindowScaleFactor), (int)(aboutWindow.DefaultHeight * Program.WindowScaleFactor)); + aboutWindow.Show(); + } + + private void Fav_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FavColumn.Value = _favToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Icon_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.IconColumn.Value = _iconToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void App_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.AppColumn.Value = _appToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Developer_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.DevColumn.Value = _developerToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Version_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.VersionColumn.Value = _versionToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void TimePlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void LastPlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileExt_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileSize_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Path_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.PathColumn.Value = _pathToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void NSP_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = _nspShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void PFS0_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = _pfs0Shown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void XCI_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = _xciShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NCA_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = _ncaShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NRO_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = _nroShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NSO_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = _nsoShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args) + { + UpdateGameTable(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.glade b/src/Ryujinx.Gtk3/UI/MainWindow.glade new file mode 100644 index 00000000..d1b6872a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/MainWindow.glade @@ -0,0 +1,1006 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkApplicationWindow" id="_mainWin"> + <property name="can-focus">False</property> + <property name="title" translatable="yes">Ryujinx</property> + <property name="window-position">center</property> + <child> + <object class="GtkBox" id="_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkMenuBar" id="_menuBar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_fileMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">File</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_loadApplicationFile"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open a file explorer to choose a Switch compatible file to load</property> + <property name="label" translatable="yes">Load Application from File</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Load_Application_File" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_loadApplicationFolder"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open a file explorer to choose a Switch compatible, unpacked application to load</property> + <property name="label" translatable="yes">Load Unpacked Game</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Load_Application_Folder" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_appletMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Load Applet</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="LoadMiiEditApplet"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open Mii Editor Applet in Standalone mode</property> + <property name="label" translatable="yes">Mii Editor</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Load_Mii_Edit_Applet" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="OpenRyuFolder"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open Ryujinx filesystem folder</property> + <property name="label" translatable="yes">Open Ryujinx Folder</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Open_Ryu_Folder" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="OpenLogsFolder"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Opens the folder where logs are written to.</property> + <property name="label" translatable="yes">Open Logs Folder</property> + <property name="use-underline">True</property> + <signal name="activate" handler="OpenLogsFolder_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="ExitMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Exit Ryujinx</property> + <property name="label" translatable="yes">Exit</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Exit_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_optionMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Options</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_fullScreen"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Enter Fullscreen</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_startFullScreen"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Start Games in Fullscreen Mode</property> + <property name="use-underline">True</property> + <signal name="toggled" handler="StartFullScreen_Toggled" swapped="no"/> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_showConsole"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Show Log Console</property> + <property name="use-underline">True</property> + <signal name="toggled" handler="ShowConsole_Toggled" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="GUIColumns"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Select which GUI columns to enable</property> + <property name="label" translatable="yes">Enable GUI Columns</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkCheckMenuItem" id="_favToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Favorite Games Column in the game list</property> + <property name="label" translatable="yes">Enable Favorite Games Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_iconToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Icon Column in the game list</property> + <property name="label" translatable="yes">Enable Icon Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_appToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Title Name/ID Column in the game list</property> + <property name="label" translatable="yes">Enable Title Name/ID Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_developerToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Developer Column in the game list</property> + <property name="label" translatable="yes">Enable Developer Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_versionToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Version Column in the game list</property> + <property name="label" translatable="yes">Enable Version Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_timePlayedToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Time Played Column in the game list</property> + <property name="label" translatable="yes">Enable Time Played Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_lastPlayedToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Last Played Column in the game list</property> + <property name="label" translatable="yes">Enable Last Played Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_fileExtToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable file extension column in the game list</property> + <property name="label" translatable="yes">Enable File Ext Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_fileSizeToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable File Size Column in the game list</property> + <property name="label" translatable="yes">Enable File Size Column</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_pathToggle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enable or Disable Path Column in the game list</property> + <property name="label" translatable="yes">Enable Path Column</property> + <property name="use-underline">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="ShownFileTypes"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Select which file types to show</property> + <property name="label" translatable="yes">Show File Types</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkCheckMenuItem" id="_nspShown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .NSP files in the games list</property> + <property name="label" translatable="yes">.NSP</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_pfs0Shown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .PFS0 files in the games list</property> + <property name="label" translatable="yes">.PFS0</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_xciShown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .XCI files in the games list</property> + <property name="label" translatable="yes">.XCI</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_ncaShown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .NCA files in the games list</property> + <property name="label" translatable="yes">.NCA</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_nroShown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .NRO files in the games list</property> + <property name="label" translatable="yes">.NRO</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="_nsoShown"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Shows .NSO files in the games list</property> + <property name="label" translatable="yes">.NSO</property> + <property name="use-underline">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="SettingsMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open settings window</property> + <property name="label" translatable="yes">Settings</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Settings_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_manageUserProfiles"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open User Profiles Manager window</property> + <property name="label" translatable="yes">Manage User Profiles</property> + <property name="use-underline">True</property> + <signal name="activate" handler="ManageUserProfiles_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_actionMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Actions</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_pauseEmulation"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Pause emulation</property> + <property name="label" translatable="yes">Pause Emulation</property> + <property name="use-underline">True</property> + <signal name="activate" handler="PauseEmulation_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_resumeEmulation"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Resume emulation</property> + <property name="label" translatable="yes">Resume Emulation</property> + <property name="use-underline">True</property> + <signal name="activate" handler="ResumeEmulation_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_stopEmulation"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Stop emulation of the current game and return to game selection</property> + <property name="label" translatable="yes">Stop Emulation</property> + <property name="use-underline">True</property> + <signal name="activate" handler="StopEmulation_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_simulateWakeUpMessage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Simulate a Wake-up Message</property> + <property name="label" translatable="yes">Simulate Wake-up Message</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Simulate_WakeUp_Message_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_scanAmiibo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Scan an Amiibo</property> + <property name="label" translatable="yes">Scan an Amiibo</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Scan_Amiibo" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_takeScreenshot"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Take a screenshot</property> + <property name="label" translatable="yes">Take Screenshot</property> + <signal name="activate" handler="Take_Screenshot" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_hideUI"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Hide UI (SHOWUIKEY to show)</property> + <property name="use-underline">True</property> + <signal name="activate" handler="HideUI_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_manageCheats"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Manage Cheats</property> + <signal name="activate" handler="ManageCheats_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_toolsMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Tools</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="FirmwareSubMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Install Firmware</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_firmwareInstallFile"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Install a firmware from XCI or ZIP</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Installer_File_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_firmwareInstallDirectory"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Install a firmware from a directory</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Installer_Directory_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_fileTypesSubMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Manage file types</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="_installFileTypes"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Install file types</property> + <signal name="activate" handler="InstallFileTypes_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkMenuItem" id="_uninstallFileTypes"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Uninstall file types</property> + <signal name="activate" handler="UninstallFileTypes_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="HelpMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Help</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkMenuItem" id="UpdateMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Check for updates to Ryujinx</property> + <property name="label" translatable="yes">Check for Updates</property> + <property name="use-underline">True</property> + <signal name="activate" handler="Update_Pressed" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="About"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Open about window</property> + <property name="label" translatable="yes">About</property> + <property name="use-underline">True</property> + <signal name="activate" handler="About_Pressed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_mainBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="_viewBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow" id="_gameTableWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkTreeView" id="_gameTable"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="reorderable">True</property> + <property name="hover-selection">True</property> + <signal name="row-activated" handler="Row_Activated" swapped="no"/> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="_gameTableSelection"/> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_footerBox"> + <property name="height-request">19</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkBox" id="_listStatusBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkEventBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="name">RefreshList</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="stock">gtk-refresh</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_progressLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">5</property> + <property name="margin-top">2</property> + <property name="margin-bottom">2</property> + <property name="label" translatable="yes">0/0 Games Loaded</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="_progressBar"> + <property name="width-request">200</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">10</property> + <property name="margin-right">5</property> + <property name="margin-bottom">6</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_statusBar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkEventBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <signal name="button-release-event" handler="VSyncStatus_Clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="_vSyncStatus"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="label" translatable="yes">VSync</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkEventBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <signal name="button-release-event" handler="DockedMode_Clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="_dockedMode"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkEventBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <signal name="button-release-event" handler="VolumeStatus_Clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="_volumeStatus"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkEventBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <signal name="button-release-event" handler="AspectRatio_Clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="_aspectRatio"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">7</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_gameStatus"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">8</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">9</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_fifoStatus"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">10</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">11</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_gpuBackend"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">12</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">13</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_gpuName"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">14</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">System Version</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_firmwareVersionLabel"> + <property name="width-request">50</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_loadingStatusLabel"> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="label" translatable="yes">0/0 </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">11</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="_loadingStatusBar"> + <property name="width-request">200</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-bottom">6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">12</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs b/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs new file mode 100644 index 00000000..1fdabc75 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs @@ -0,0 +1,142 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Input.HLE; +using SPB.Graphics; +using SPB.Graphics.Exceptions; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.WGL; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI +{ + public partial class OpenGLRenderer : RendererWidgetBase + { + private readonly GraphicsDebugLevel _glLogLevel; + + private bool _initializedOpenGL; + + private OpenGLContextBase _openGLContext; + private SwappableNativeWindowBase _nativeWindow; + + public OpenGLRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) + { + _glLogLevel = glLogLevel; + } + + protected override bool OnDrawn(Cairo.Context cr) + { + if (!_initializedOpenGL) + { + IntializeOpenGL(); + } + + return true; + } + + private void IntializeOpenGL() + { + _nativeWindow = RetrieveNativeWindow(); + + Window.EnsureNative(); + + _openGLContext = PlatformHelper.CreateOpenGLContext(GetGraphicsMode(), 3, 3, _glLogLevel == GraphicsDebugLevel.None ? OpenGLContextFlags.Compat : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug); + _openGLContext.Initialize(_nativeWindow); + _openGLContext.MakeCurrent(_nativeWindow); + + // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread. + _openGLContext.MakeCurrent(null); + + WaitEvent.Set(); + + _initializedOpenGL = true; + } + + private SwappableNativeWindowBase RetrieveNativeWindow() + { + if (OperatingSystem.IsWindows()) + { + IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); + + return new WGLWindow(new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); + IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); + + return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); + } + + throw new NotImplementedException(); + } + + [LibraryImport("libgdk-3-0.dll")] + private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); + + private static FramebufferFormat GetGraphicsMode() + { + return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; + } + + public override void InitializeRenderer() + { + // First take exclusivity on the OpenGL context. + ((Graphics.OpenGL.OpenGLRenderer)Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(_openGLContext)); + + _openGLContext.MakeCurrent(_nativeWindow); + + GL.ClearColor(0, 0, 0, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit); + SwapBuffers(); + } + + public override void SwapBuffers() + { + _nativeWindow.SwapBuffers(); + } + + protected override string GetGpuBackendName() + { + return "OpenGL"; + } + + protected override void Dispose(bool disposing) + { + // Try to bind the OpenGL context before calling the shutdown event. + try + { + _openGLContext?.MakeCurrent(_nativeWindow); + } + catch (ContextException e) + { + Logger.Warning?.Print(LogClass.UI, $"Failed to bind OpenGL context: {e}"); + } + + Device?.DisposeGpu(); + NpadManager.Dispose(); + + // Unbind context and destroy everything. + try + { + _openGLContext?.MakeCurrent(null); + } + catch (ContextException e) + { + Logger.Warning?.Print(LogClass.UI, $"Failed to unbind OpenGL context: {e}"); + } + + _openGLContext?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs b/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs new file mode 100644 index 00000000..1224ccfe --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs @@ -0,0 +1,20 @@ +using SPB.Graphics; +using System; + +namespace Ryujinx.UI +{ + public class OpenToolkitBindingsContext : OpenTK.IBindingsContext + { + private readonly IBindingsContext _bindingContext; + + public OpenToolkitBindingsContext(IBindingsContext bindingsContext) + { + _bindingContext = bindingsContext; + } + + public IntPtr GetProcAddress(string procName) + { + return _bindingContext.GetProcAddress(procName); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs new file mode 100644 index 00000000..e27d0604 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs @@ -0,0 +1,803 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Input; +using Ryujinx.Input.GTK3; +using Ryujinx.Input.HLE; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Widgets; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Image = SixLabors.ImageSharp.Image; +using Key = Ryujinx.Input.Key; +using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.UI +{ + public abstract class RendererWidgetBase : DrawingArea + { + private const int SwitchPanelWidth = 1280; + private const int SwitchPanelHeight = 720; + private const int TargetFps = 60; + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const float VolumeDelta = 0.05f; + + public ManualResetEvent WaitEvent { get; set; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; private set; } + public IRenderer Renderer { get; private set; } + + public bool ScreenshotRequested { get; set; } + protected int WindowWidth { get; private set; } + protected int WindowHeight { get; private set; } + + public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; + + private bool _isActive; + private bool _isStopped; + + private bool _toggleFullscreen; + private bool _toggleDockedMode; + + private readonly long _ticksPerFrame; + + private long _ticks = 0; + private float _newVolume; + + private readonly Stopwatch _chrono; + + private KeyboardHotkeyState _prevHotkeyState; + + private readonly ManualResetEvent _exitEvent; + private readonly ManualResetEvent _gpuDoneEvent; + + private readonly CancellationTokenSource _gpuCancellationTokenSource; + + // Hide Cursor + const int CursorHideIdleTime = 5; // seconds + private static readonly Cursor _invisibleCursor = new(Display.Default, CursorType.BlankCursor); + private long _lastCursorMoveTime; + private HideCursorMode _hideCursorMode; + private readonly InputManager _inputManager; + private readonly IKeyboard _keyboardInterface; + private readonly GraphicsDebugLevel _glLogLevel; + private string _gpuBackendName; + private string _gpuDriverName; + private bool _isMouseInClient; + + public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel) + { + var mouseDriver = new GTK3MouseDriver(this); + + _inputManager = inputManager; + _inputManager.SetMouseDriver(mouseDriver); + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + WaitEvent = new ManualResetEvent(false); + + _glLogLevel = glLogLevel; + + Destroyed += Renderer_Destroyed; + + _chrono = new Stopwatch(); + + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + AddEvents((int)(EventMask.ButtonPressMask + | EventMask.ButtonReleaseMask + | EventMask.PointerMotionMask + | EventMask.ScrollMask + | EventMask.EnterNotifyMask + | EventMask.LeaveNotifyMask + | EventMask.KeyPressMask + | EventMask.KeyReleaseMask)); + + _exitEvent = new ManualResetEvent(false); + _gpuDoneEvent = new ManualResetEvent(false); + + _gpuCancellationTokenSource = new CancellationTokenSource(); + + _hideCursorMode = ConfigurationState.Instance.HideCursor; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + ConfigurationState.Instance.HideCursor.Event += HideCursorStateChanged; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + } + + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e) + { + Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e) + { + Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + public abstract void InitializeRenderer(); + + public abstract void SwapBuffers(); + + protected abstract string GetGpuBackendName(); + + private string GetGpuDriverName() + { + return Renderer.GetHardwareInfo().GpuDriver; + } + + private void HideCursorStateChanged(object sender, ReactiveEventArgs<HideCursorMode> state) + { + Application.Invoke(delegate + { + _hideCursorMode = state.NewValue; + + switch (_hideCursorMode) + { + case HideCursorMode.Never: + Window.Cursor = null; + break; + case HideCursorMode.OnIdle: + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + break; + case HideCursorMode.Always: + Window.Cursor = _invisibleCursor; + break; + default: + throw new ArgumentOutOfRangeException(nameof(state)); + } + }); + } + + private void Renderer_Destroyed(object sender, EventArgs e) + { + ConfigurationState.Instance.HideCursor.Event -= HideCursorStateChanged; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + + NpadManager.Dispose(); + Dispose(); + } + + private void UpdateAnriAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e) + { + Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + protected override bool OnMotionNotifyEvent(EventMotion evnt) + { + if (_hideCursorMode == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + if (ConfigurationState.Instance.Hid.EnableMouse) + { + Window.Cursor = _invisibleCursor; + } + + _isMouseInClient = true; + + return false; + } + + protected override bool OnEnterNotifyEvent(EventCrossing evnt) + { + Window.Cursor = ConfigurationState.Instance.Hid.EnableMouse ? _invisibleCursor : null; + + _isMouseInClient = true; + + return base.OnEnterNotifyEvent(evnt); + } + + protected override bool OnLeaveNotifyEvent(EventCrossing evnt) + { + Window.Cursor = null; + + _isMouseInClient = false; + + return base.OnLeaveNotifyEvent(evnt); + } + + protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight) + { + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + // If the monitor is at least 1080p, use the Switch panel size as minimal size. + if (monitor.Geometry.Height >= 1080) + { + minimumHeight = SwitchPanelHeight; + } + // Otherwise, we default minimal size to 480p 16:9. + else + { + minimumHeight = 480; + } + + naturalHeight = minimumHeight; + } + + protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth) + { + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + // If the monitor is at least 1080p, use the Switch panel size as minimal size. + if (monitor.Geometry.Height >= 1080) + { + minimumWidth = SwitchPanelWidth; + } + // Otherwise, we default minimal size to 480p 16:9. + else + { + minimumWidth = 854; + } + + naturalWidth = minimumWidth; + } + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + bool result = base.OnConfigureEvent(evnt); + + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + WindowWidth = evnt.Width * monitor.ScaleFactor; + WindowHeight = evnt.Height * monitor.ScaleFactor; + + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); + + return result; + } + + private void HandleScreenState(KeyboardStateSnapshot keyboard) + { + bool toggleFullscreen = keyboard.IsPressed(Key.F11) + || ((keyboard.IsPressed(Key.AltLeft) + || keyboard.IsPressed(Key.AltRight)) + && keyboard.IsPressed(Key.Enter)) + || keyboard.IsPressed(Key.Escape); + + bool fullScreenToggled = ParentWindow.State.HasFlag(WindowState.Fullscreen); + + if (toggleFullscreen != _toggleFullscreen) + { + if (toggleFullscreen) + { + if (fullScreenToggled) + { + ParentWindow.Unfullscreen(); + (Toplevel as MainWindow)?.ToggleExtraWidgets(true); + } + else + { + if (keyboard.IsPressed(Key.Escape)) + { + if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + Exit(); + } + } + else + { + ParentWindow.Fullscreen(); + (Toplevel as MainWindow)?.ToggleExtraWidgets(false); + } + } + } + } + + _toggleFullscreen = toggleFullscreen; + + bool toggleDockedMode = keyboard.IsPressed(Key.F9); + + if (toggleDockedMode != _toggleDockedMode) + { + if (toggleDockedMode) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = + !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + } + + _toggleDockedMode = toggleDockedMode; + + if (_isMouseInClient) + { + if (ConfigurationState.Instance.Hid.EnableMouse.Value) + { + Window.Cursor = _invisibleCursor; + } + else + { + switch (_hideCursorMode) + { + case HideCursorMode.OnIdle: + long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; + Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null; + break; + case HideCursorMode.Always: + Window.Cursor = _invisibleCursor; + break; + case HideCursorMode.Never: + Window.Cursor = null; + break; + } + } + } + } + + public void Initialize(Switch device) + { + Device = device; + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + Renderer = renderer; + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); + + if (Renderer != null) + { + Renderer.ScreenCaptured += Renderer_ScreenCaptured; + } + + NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(device); + } + + private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (this) + { + var currentTime = DateTime.Now; + string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), + }; + + string path = System.IO.Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) + : Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); + + if (e.FlipX) + { + image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + if (e.FlipY) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + + image.SaveAsPng(path, new PngEncoder() + { + ColorType = PngColorType.Rgb, + }); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Render() + { + Gtk.Window parent = Toplevel as Gtk.Window; + parent.Present(); + + InitializeRenderer(); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + Renderer.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + Renderer.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + + _gpuBackendName = GetGpuBackendName(); + _gpuDriverName = GetGpuDriverName(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + + Renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + (Toplevel as MainWindow)?.ActivatePauseMenu(); + + while (_isActive) + { + if (_isStopped) + { + return; + } + + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + Device.PresentFrame(SwapBuffers); + } + + if (_ticks >= _ticksPerFrame) + { + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld"; + float scale = GraphicsConfig.ResScale; + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + Device.GetVolume(), + _gpuBackendName, + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", + $"GPU: {_gpuDriverName}")); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + } + + public void Start() + { + _chrono.Restart(); + + _isActive = true; + + Gtk.Window parent = Toplevel as Gtk.Window; + + Application.Invoke(delegate + { + parent.Present(); + + var activeProcess = Device.Processes.ActiveApplication; + + parent.Title = TitleHelper.ActiveApplicationTitle(activeProcess, Program.Version); + }); + + Thread renderLoopThread = new(Render) + { + Name = "GUI.RenderLoop", + }; + renderLoopThread.Start(); + + Thread nvidiaStutterWorkaround = null; + if (Renderer is Graphics.OpenGL.OpenGLRenderer) + { + nvidiaStutterWorkaround = new Thread(NvidiaStutterWorkaround) + { + Name = "GUI.NvidiaStutterWorkaround", + }; + nvidiaStutterWorkaround.Start(); + } + + MainLoop(); + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + nvidiaStutterWorkaround?.Join(); + + Exit(); + } + + public void Exit() + { + TouchScreenManager?.Dispose(); + NpadManager?.Dispose(); + + if (_isStopped) + { + return; + } + + _gpuCancellationTokenSource.Cancel(); + + _isStopped = true; + + if (_isActive) + { + _isActive = false; + + _exitEvent.WaitOne(); + _exitEvent.Dispose(); + } + } + + private void NvidiaStutterWorkaround() + { + while (_isActive) + { + // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. + // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. + // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ. + // This creates a new thread every second or so. + // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics. + // This is a little over budget on a frame time of 16ms, so creates a large stutter. + // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread. + + // TODO: This should be removed when the issue with the GateThread is resolved. + + ThreadPool.QueueUserWorkItem((state) => { }); + Thread.Sleep(300); + } + } + + public void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + + _exitEvent.Set(); + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return true; + } + + if (_isStopped) + { + return false; + } + + if ((Toplevel as MainWindow).IsFocused) + { + Application.Invoke(delegate + { + KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); + + HandleScreenState(keyboard); + + if (keyboard.IsPressed(Key.Delete)) + { + if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + } + }); + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if ((Toplevel as MainWindow).IsFocused) + { + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync)) + { + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + } + + if ((currentHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot)) || ScreenshotRequested) + { + ScreenshotRequested = false; + + Renderer.Screenshot(); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI)) + { + (Toplevel as MainWindow).ToggleExtraWidgets(true); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.Pause) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Pause)) + { + (Toplevel as MainWindow)?.TogglePause(); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute)) + { + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp)) + { + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown)) + { + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp)) + { + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown)) + { + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + } + + _prevHotkeyState = currentHotkeyState; + } + + // Touchscreen + bool hasTouch = false; + + // Get screen touch position + if ((Toplevel as MainWindow).IsFocused && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as GTK3MouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + TouchScreenManager.Update(false); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + [Flags] + private enum KeyboardHotkeyState + { + None = 0, + ToggleVSync = 1 << 0, + Screenshot = 1 << 1, + ShowUI = 1 << 2, + Pause = 1 << 3, + ToggleMute = 1 << 4, + ResScaleUp = 1 << 5, + ResScaleDown = 1 << 6, + VolumeUp = 1 << 7, + VolumeDown = 1 << 8, + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state |= KeyboardHotkeyState.ToggleVSync; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state |= KeyboardHotkeyState.Screenshot; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) + { + state |= KeyboardHotkeyState.ShowUI; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state |= KeyboardHotkeyState.Pause; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state |= KeyboardHotkeyState.ToggleMute; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state |= KeyboardHotkeyState.ResScaleUp; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state |= KeyboardHotkeyState.ResScaleDown; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state |= KeyboardHotkeyState.VolumeUp; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state |= KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs b/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs new file mode 100644 index 00000000..97feb434 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.OpenGL; +using SPB.Graphics; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Windowing; + +namespace Ryujinx.UI +{ + class SPBOpenGLContext : IOpenGLContext + { + private readonly OpenGLContextBase _context; + private readonly NativeWindowBase _window; + + private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) + { + _context = context; + _window = window; + } + + public void Dispose() + { + _context.Dispose(); + _window.Dispose(); + } + + public void MakeCurrent() + { + _context.MakeCurrent(_window); + } + + public bool HasContext() => _context.IsCurrent; + + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) + { + OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); + NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); + + context.Initialize(window); + context.MakeCurrent(window); + + GL.LoadBindings(new OpenToolkitBindingsContext(context)); + + context.MakeCurrent(null); + + return new SPBOpenGLContext(context, window); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs b/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs new file mode 100644 index 00000000..db467ebf --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.UI +{ + public class StatusUpdatedEventArgs : EventArgs + { + public bool VSyncEnabled; + public float Volume; + public string DockedMode; + public string AspectRatio; + public string GameStatus; + public string FifoStatus; + public string GpuName; + public string GpuBackend; + + public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + { + VSyncEnabled = vSyncEnabled; + Volume = volume; + GpuBackend = gpuBackend; + DockedMode = dockedMode; + AspectRatio = aspectRatio; + GameStatus = gameStatus; + FifoStatus = fifoStatus; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs b/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs new file mode 100644 index 00000000..eefc5699 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs @@ -0,0 +1,93 @@ +using Gdk; +using Ryujinx.Common.Configuration; +using Ryujinx.Input.HLE; +using Ryujinx.UI.Helper; +using SPB.Graphics.Vulkan; +using SPB.Platform.Metal; +using SPB.Platform.Win32; +using SPB.Platform.X11; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI +{ + public partial class VulkanRenderer : RendererWidgetBase + { + public NativeWindowBase NativeWindow { get; private set; } + private UpdateBoundsCallbackDelegate _updateBoundsCallback; + + public VulkanRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { } + + private NativeWindowBase RetrieveNativeWindow() + { + if (OperatingSystem.IsWindows()) + { + IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); + + return new SimpleWin32Window(new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); + IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); + + return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsMacOS()) + { + IntPtr metalLayer = MetalHelper.GetMetalLayer(Display, Window, out IntPtr nsView, out _updateBoundsCallback); + + return new SimpleMetalWindow(new NativeHandle(nsView), new NativeHandle(metalLayer)); + } + + throw new NotImplementedException(); + } + + [LibraryImport("libgdk-3-0.dll")] + private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + if (NativeWindow == null) + { + NativeWindow = RetrieveNativeWindow(); + + WaitEvent.Set(); + } + + bool result = base.OnConfigureEvent(evnt); + + _updateBoundsCallback?.Invoke(Window); + + return result; + } + + public unsafe IntPtr CreateWindowSurface(IntPtr instance) + { + return VulkanHelper.CreateWindowSurface(instance, NativeWindow); + } + + public override void InitializeRenderer() { } + + public override void SwapBuffers() { } + + protected override string GetGpuBackendName() + { + return "Vulkan"; + } + + protected override void Dispose(bool disposing) + { + Device?.DisposeGpu(); + + NpadManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs new file mode 100644 index 00000000..8ee1cd2f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs @@ -0,0 +1,233 @@ +using Gtk; +using System; + +namespace Ryujinx.UI.Widgets +{ + public partial class GameTableContextMenu : Menu + { + private MenuItem _openSaveUserDirMenuItem; + private MenuItem _openSaveDeviceDirMenuItem; + private MenuItem _openSaveBcatDirMenuItem; + private MenuItem _manageTitleUpdatesMenuItem; + private MenuItem _manageDlcMenuItem; + private MenuItem _manageCheatMenuItem; + private MenuItem _openTitleModDirMenuItem; + private MenuItem _openTitleSdModDirMenuItem; + private Menu _extractSubMenu; + private MenuItem _extractMenuItem; + private MenuItem _extractRomFsMenuItem; + private MenuItem _extractExeFsMenuItem; + private MenuItem _extractLogoMenuItem; + private Menu _manageSubMenu; + private MenuItem _manageCacheMenuItem; + private MenuItem _purgePtcCacheMenuItem; + private MenuItem _purgeShaderCacheMenuItem; + private MenuItem _openPtcDirMenuItem; + private MenuItem _openShaderCacheDirMenuItem; + private MenuItem _createShortcutMenuItem; + + private void InitializeComponent() + { + // + // _openSaveUserDirMenuItem + // + _openSaveUserDirMenuItem = new MenuItem("Open User Save Directory") + { + TooltipText = "Open the directory which contains Application's User Saves.", + }; + _openSaveUserDirMenuItem.Activated += OpenSaveUserDir_Clicked; + + // + // _openSaveDeviceDirMenuItem + // + _openSaveDeviceDirMenuItem = new MenuItem("Open Device Save Directory") + { + TooltipText = "Open the directory which contains Application's Device Saves.", + }; + _openSaveDeviceDirMenuItem.Activated += OpenSaveDeviceDir_Clicked; + + // + // _openSaveBcatDirMenuItem + // + _openSaveBcatDirMenuItem = new MenuItem("Open BCAT Save Directory") + { + TooltipText = "Open the directory which contains Application's BCAT Saves.", + }; + _openSaveBcatDirMenuItem.Activated += OpenSaveBcatDir_Clicked; + + // + // _manageTitleUpdatesMenuItem + // + _manageTitleUpdatesMenuItem = new MenuItem("Manage Title Updates") + { + TooltipText = "Open the Title Update management window", + }; + _manageTitleUpdatesMenuItem.Activated += ManageTitleUpdates_Clicked; + + // + // _manageDlcMenuItem + // + _manageDlcMenuItem = new MenuItem("Manage DLC") + { + TooltipText = "Open the DLC management window", + }; + _manageDlcMenuItem.Activated += ManageDlc_Clicked; + + // + // _manageCheatMenuItem + // + _manageCheatMenuItem = new MenuItem("Manage Cheats") + { + TooltipText = "Open the Cheat management window", + }; + _manageCheatMenuItem.Activated += ManageCheats_Clicked; + + // + // _openTitleModDirMenuItem + // + _openTitleModDirMenuItem = new MenuItem("Open Mods Directory") + { + TooltipText = "Open the directory which contains Application's Mods.", + }; + _openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked; + + // + // _openTitleSdModDirMenuItem + // + _openTitleSdModDirMenuItem = new MenuItem("Open Atmosphere Mods Directory") + { + TooltipText = "Open the alternative SD card atmosphere directory which contains the Application's Mods.", + }; + _openTitleSdModDirMenuItem.Activated += OpenTitleSdModDir_Clicked; + + // + // _extractSubMenu + // + _extractSubMenu = new Menu(); + + // + // _extractMenuItem + // + _extractMenuItem = new MenuItem("Extract Data") + { + Submenu = _extractSubMenu + }; + + // + // _extractRomFsMenuItem + // + _extractRomFsMenuItem = new MenuItem("RomFS") + { + TooltipText = "Extract the RomFS section from Application's current config (including updates).", + }; + _extractRomFsMenuItem.Activated += ExtractRomFs_Clicked; + + // + // _extractExeFsMenuItem + // + _extractExeFsMenuItem = new MenuItem("ExeFS") + { + TooltipText = "Extract the ExeFS section from Application's current config (including updates).", + }; + _extractExeFsMenuItem.Activated += ExtractExeFs_Clicked; + + // + // _extractLogoMenuItem + // + _extractLogoMenuItem = new MenuItem("Logo") + { + TooltipText = "Extract the Logo section from Application's current config (including updates).", + }; + _extractLogoMenuItem.Activated += ExtractLogo_Clicked; + + // + // _manageSubMenu + // + _manageSubMenu = new Menu(); + + // + // _manageCacheMenuItem + // + _manageCacheMenuItem = new MenuItem("Cache Management") + { + Submenu = _manageSubMenu, + }; + + // + // _purgePtcCacheMenuItem + // + _purgePtcCacheMenuItem = new MenuItem("Queue PPTC Rebuild") + { + TooltipText = "Trigger PPTC to rebuild at boot time on the next game launch.", + }; + _purgePtcCacheMenuItem.Activated += PurgePtcCache_Clicked; + + // + // _purgeShaderCacheMenuItem + // + _purgeShaderCacheMenuItem = new MenuItem("Purge Shader Cache") + { + TooltipText = "Delete the Application's shader cache.", + }; + _purgeShaderCacheMenuItem.Activated += PurgeShaderCache_Clicked; + + // + // _openPtcDirMenuItem + // + _openPtcDirMenuItem = new MenuItem("Open PPTC Directory") + { + TooltipText = "Open the directory which contains the Application's PPTC cache.", + }; + _openPtcDirMenuItem.Activated += OpenPtcDir_Clicked; + + // + // _openShaderCacheDirMenuItem + // + _openShaderCacheDirMenuItem = new MenuItem("Open Shader Cache Directory") + { + TooltipText = "Open the directory which contains the Application's shader cache.", + }; + _openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked; + + // + // _createShortcutMenuItem + // + _createShortcutMenuItem = new MenuItem("Create Application Shortcut") + { + TooltipText = OperatingSystem.IsMacOS() ? "Create a shortcut in macOS's Applications folder that launches the selected Application" : "Create a Desktop Shortcut that launches the selected Application." + }; + _createShortcutMenuItem.Activated += CreateShortcut_Clicked; + + ShowComponent(); + } + + private void ShowComponent() + { + _extractSubMenu.Append(_extractExeFsMenuItem); + _extractSubMenu.Append(_extractRomFsMenuItem); + _extractSubMenu.Append(_extractLogoMenuItem); + + _manageSubMenu.Append(_purgePtcCacheMenuItem); + _manageSubMenu.Append(_purgeShaderCacheMenuItem); + _manageSubMenu.Append(_openPtcDirMenuItem); + _manageSubMenu.Append(_openShaderCacheDirMenuItem); + + Add(_createShortcutMenuItem); + Add(new SeparatorMenuItem()); + Add(_openSaveUserDirMenuItem); + Add(_openSaveDeviceDirMenuItem); + Add(_openSaveBcatDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageTitleUpdatesMenuItem); + Add(_manageDlcMenuItem); + Add(_manageCheatMenuItem); + Add(_openTitleModDirMenuItem); + Add(_openTitleSdModDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageCacheMenuItem); + Add(_extractMenuItem); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs new file mode 100644 index 00000000..c8236223 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs @@ -0,0 +1,644 @@ +using Gtk; +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +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.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Windows; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading; + +namespace Ryujinx.UI.Widgets +{ + public partial class GameTableContextMenu : Menu + { + private readonly MainWindow _parent; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly AccountManager _accountManager; + private readonly HorizonClient _horizonClient; + private readonly BlitStruct<ApplicationControlProperty> _controlData; + + private readonly string _titleFilePath; + private readonly string _titleName; + private readonly string _titleIdText; + private readonly ulong _titleId; + + private MessageDialog _dialog; + private bool _cancel; + + public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData) + { + _parent = parent; + + InitializeComponent(); + + _virtualFileSystem = virtualFileSystem; + _accountManager = accountManager; + _horizonClient = horizonClient; + _titleFilePath = titleFilePath; + _titleName = titleName; + _titleIdText = titleId; + _controlData = controlData; + + if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId)) + { + GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id"); + + return; + } + + _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; + _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; + _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; + + string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); + bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; + + _extractRomFsMenuItem.Sensitive = hasNca; + _extractExeFsMenuItem.Sensitive = hasNca; + _extractLogoMenuItem.Sensitive = hasNca; + + _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild; + + PopupAtPointer(null); + } + + private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId) + { + saveDataId = default; + + Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); + + if (ResultFs.TargetNotFound.Includes(result)) + { + ref ApplicationControlProperty control = ref controlHolder.Value; + + Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]"); + + if (Utilities.IsZeros(controlHolder.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct<ApplicationControlProperty>(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); + + result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user); + + if (result.IsFailure()) + { + GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + // Try to find the savedata again after creating it + result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); + } + + if (result.IsSuccess()) + { + saveDataId = saveDataInfo.SaveDataId; + + return true; + } + + GtkDialog.CreateErrorDialog($"There was an error finding the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + private void OpenSaveDir(in SaveDataFilter saveDataFilter) + { + if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId)) + { + return; + } + + string saveRootPath = System.IO.Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); + + if (!Directory.Exists(saveRootPath)) + { + // Inconsistent state. Create the directory + Directory.CreateDirectory(saveRootPath); + } + + string committedPath = System.IO.Path.Combine(saveRootPath, "0"); + string workingPath = System.IO.Path.Combine(saveRootPath, "1"); + + // If the committed directory exists, that path will be loaded the next time the savedata is mounted + if (Directory.Exists(committedPath)) + { + OpenHelper.OpenFolder(committedPath); + } + else + { + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } + + OpenHelper.OpenFolder(workingPath); + } + } + + private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) + { + FileChooserNative fileChooser = new("Choose the folder to extract into", _parent, FileChooserAction.SelectFolder, "Extract", "Cancel"); + + ResponseType response = (ResponseType)fileChooser.Run(); + string destination = fileChooser.Filename; + + fileChooser.Dispose(); + + if (response == ResponseType.Accept) + { + Thread extractorThread = new(() => + { + Gtk.Application.Invoke(delegate + { + _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) + { + Title = "Ryujinx - NCA Section Extractor", + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"), + SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", + WindowPosition = WindowPosition.Center, + }; + + int dialogResponse = _dialog.Run(); + if (dialogResponse == (int)ResponseType.Cancel || dialogResponse == (int)ResponseType.DeleteEvent) + { + _cancel = true; + _dialog.Dispose(); + } + }); + + using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read); + + Nca mainNca = null; + Nca patchNca = null; + + if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") || + (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") || + (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci")) + { + IFileSystem pfs; + + if (System.IO.Path.GetExtension(_titleFilePath) == ".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<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Release().AsStorage()); + + 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 (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca") + { + mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage()); + } + + if (mainNca == null) + { + Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA is not present in the selected file."); + + Gtk.Application.Invoke(delegate + { + GtkDialog.CreateErrorDialog("Extraction failure. The main NCA is not present in the selected file."); + }); + + return; + } + + (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); + + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType); + + bool sectionExistsInPatch = false; + + if (patchNca != null) + { + sectionExistsInPatch = patchNca.CanOpenSection(index); + } + + IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) + : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); + + FileSystemClient fsClient = _horizonClient.Fs; + + string source = DateTime.Now.ToFileTime().ToString()[10..]; + string output = DateTime.Now.ToFileTime().ToString()[10..]; + + using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem); + using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination)); + + fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); + fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); + + (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/"); + + if (!canceled) + { + if (resultCode.Value.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); + + Gtk.Application.Invoke(delegate + { + _dialog?.Dispose(); + + GtkDialog.CreateErrorDialog("Extraction failed. Read the log file for further information."); + }); + } + else if (resultCode.Value.IsSuccess()) + { + Gtk.Application.Invoke(delegate + { + _dialog?.Dispose(); + + MessageDialog dialog = new(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) + { + Title = "Ryujinx - NCA Section Extractor", + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), + SecondaryText = "Extraction completed successfully.", + WindowPosition = WindowPosition.Center, + }; + + dialog.Run(); + dialog.Dispose(); + }); + } + } + + fsClient.Unmount(source.ToU8Span()); + fsClient.Unmount(output.ToU8Span()); + }) + { + Name = "GUI.NcaSectionExtractorThread", + IsBackground = true, + }; + extractorThread.Start(); + } + } + + private (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath) + { + Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) + { + return (rc, false); + } + + using (sourceHandle) + { + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) + { + if (_cancel) + { + return (null, true); + } + + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) + { + fs.EnsureDirectoryExists(subDstPath); + + (Result? result, bool canceled) = CopyDirectory(fs, subSrcPath, subDstPath); + if (canceled || result.Value.IsFailure()) + { + return (result, canceled); + } + } + + if (entry.Type == DirectoryEntryType.File) + { + fs.CreateOrOverwriteFile(subDstPath, entry.Size); + + rc = CopyFile(fs, subSrcPath, subDstPath); + if (rc.IsFailure()) + { + return (rc, false); + } + } + } + } + + return (Result.Success, false); + } + + public static Result CopyFile(FileSystemClient fs, string sourcePath, string destPath) + { + Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); + if (rc.IsFailure()) + { + return rc; + } + + using (sourceHandle) + { + rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend); + if (rc.IsFailure()) + { + return rc; + } + + using (destHandle) + { + const int MaxBufferSize = 1024 * 1024; + + rc = fs.GetFileSize(out long fileSize, sourceHandle); + if (rc.IsFailure()) + { + return rc; + } + + int bufferSize = (int)Math.Min(MaxBufferSize, fileSize); + + byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); + try + { + for (long offset = 0; offset < fileSize; offset += bufferSize) + { + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span<byte> buf = buffer.AsSpan(0, toRead); + + rc = fs.ReadFile(out long _, sourceHandle, offset, buf); + if (rc.IsFailure()) + { + return rc; + } + + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); + if (rc.IsFailure()) + { + return rc; + } + } + } + finally + { + ArrayPool<byte>.Shared.Return(buffer); + } + + rc = fs.FlushFile(destHandle); + if (rc.IsFailure()) + { + return rc; + } + } + } + + return Result.Success; + } + + // + // Events + // + private void OpenSaveUserDir_Clicked(object sender, EventArgs args) + { + var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); + var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args) + { + var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) + { + var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void ManageTitleUpdates_Clicked(object sender, EventArgs args) + { + new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show(); + } + + private void ManageDlc_Clicked(object sender, EventArgs args) + { + new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show(); + } + + private void ManageCheats_Clicked(object sender, EventArgs args) + { + new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show(); + } + + private void OpenTitleModDir_Clicked(object sender, EventArgs args) + { + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText); + + OpenHelper.OpenFolder(titleModsPath); + } + + private void OpenTitleSdModDir_Clicked(object sender, EventArgs args) + { + string sdModsBasePath = ModLoader.GetSdModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText); + + OpenHelper.OpenFolder(titleModsPath); + } + + private void ExtractRomFs_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Data); + } + + private void ExtractExeFs_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Code); + } + + private void ExtractLogo_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Logo); + } + + private void OpenPtcDir_Clicked(object sender, EventArgs args) + { + string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu"); + + string mainPath = System.IO.Path.Combine(ptcDir, "0"); + string backupPath = System.IO.Path.Combine(ptcDir, "1"); + + if (!Directory.Exists(ptcDir)) + { + Directory.CreateDirectory(ptcDir); + Directory.CreateDirectory(mainPath); + Directory.CreateDirectory(backupPath); + } + + OpenHelper.OpenFolder(ptcDir); + } + + private void OpenShaderCacheDir_Clicked(object sender, EventArgs args) + { + string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"); + + if (!Directory.Exists(shaderCacheDir)) + { + Directory.CreateDirectory(shaderCacheDir); + } + + OpenHelper.OpenFolder(shaderCacheDir); + } + + private void PurgePtcCache_Clicked(object sender, EventArgs args) + { + DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0")); + DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1")); + + MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?"); + + List<FileInfo> cacheFiles = new(); + + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } + + if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) + { + foreach (FileInfo file in cacheFiles) + { + try + { + file.Delete(); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}"); + } + } + } + + warningDialog.Dispose(); + } + + private void PurgeShaderCache_Clicked(object sender, EventArgs args) + { + DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader")); + + using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?"); + + List<DirectoryInfo> oldCacheDirectories = new(); + List<FileInfo> newCacheFiles = new(); + + if (shaderCacheDir.Exists) + { + oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + } + + if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && warningDialog.Run() == (int)ResponseType.Yes) + { + foreach (DirectoryInfo directory in oldCacheDirectories) + { + try + { + directory.Delete(true); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging shader cache at {directory.Name}: {e}"); + } + } + + foreach (FileInfo file in newCacheFiles) + { + try + { + file.Delete(); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging shader cache at {file.Name}: {e}"); + } + } + } + } + + private void CreateShortcut_Clicked(object sender, EventArgs args) + { + byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language); + ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs new file mode 100644 index 00000000..567f9ad6 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs @@ -0,0 +1,114 @@ +using Gtk; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.UI.Widgets +{ + internal class GtkDialog : MessageDialog + { + private static bool _isChoiceDialogOpen; + + private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) + : base(null, DialogFlags.Modal, messageType, buttonsType, null) + { + Title = title; + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Text = mainText; + SecondaryText = secondaryText; + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; + + Response += GtkDialog_Response; + + SetSizeRequest(200, 20); + } + + private void GtkDialog_Response(object sender, ResponseArgs args) + { + Dispose(); + } + + internal static void CreateInfoDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText) + { + return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None); + } + + internal static void CreateWarningDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Warning", mainText, secondaryText, MessageType.Warning).Run(); + } + + internal static void CreateErrorDialog(string errorMessage) + { + Logger.Error?.Print(LogClass.Application, errorMessage); + + new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run(); + } + + internal static MessageDialog CreateConfirmationDialog(string mainText, string secondaryText = "") + { + return new GtkDialog("Ryujinx - Confirmation", mainText, secondaryText, MessageType.Question, ButtonsType.YesNo); + } + + internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText) + { + if (_isChoiceDialogOpen) + { + return false; + } + + _isChoiceDialogOpen = true; + + ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run(); + + _isChoiceDialogOpen = false; + + return response == ResponseType.Yes; + } + + internal static ResponseType CreateCustomDialog(string title, string mainText, string secondaryText, Dictionary<int, string> buttons, MessageType messageType = MessageType.Other) + { + GtkDialog gtkDialog = new(title, mainText, secondaryText, messageType, ButtonsType.None); + + foreach (var button in buttons) + { + gtkDialog.AddButton(button.Value, button.Key); + } + + return (ResponseType)gtkDialog.Run(); + } + + internal static string CreateInputDialog(Window parent, string title, string mainText, uint inputMax) + { + GtkInputDialog gtkDialog = new(parent, title, mainText, inputMax); + ResponseType response = (ResponseType)gtkDialog.Run(); + string responseText = gtkDialog.InputEntry.Text.TrimEnd(); + + gtkDialog.Dispose(); + + if (response == ResponseType.Ok) + { + return responseText; + } + + return ""; + } + + internal static bool CreateExitDialog() + { + return CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to close Ryujinx?", "All unsaved data will be lost!"); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs new file mode 100644 index 00000000..fd85248b --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs @@ -0,0 +1,37 @@ +using Gtk; + +namespace Ryujinx.UI.Widgets +{ + public class GtkInputDialog : MessageDialog + { + public Entry InputEntry { get; } + + public GtkInputDialog(Window parent, string title, string mainText, uint inputMax) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.OkCancel, null) + { + SetDefaultSize(300, 0); + + Title = title; + + Label mainTextLabel = new() + { + Text = mainText, + }; + + InputEntry = new Entry + { + MaxLength = (int)inputMax, + }; + + Label inputMaxTextLabel = new() + { + Text = $"(Max length: {inputMax})", + }; + + ((Box)MessageArea).PackStart(mainTextLabel, true, true, 0); + ((Box)MessageArea).PackStart(InputEntry, true, true, 5); + ((Box)MessageArea).PackStart(inputMaxTextLabel, true, true, 0); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs new file mode 100644 index 00000000..3b3e2fbb --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs @@ -0,0 +1,57 @@ +using Gtk; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Reflection; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Widgets +{ + public class ProfileDialog : Dialog + { + public string FileName { get; private set; } + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Entry _profileEntry; + [GUI] Label _errorMessage; +#pragma warning restore CS0649, IDE0044 + + public ProfileDialog() : this(new Builder("Ryujinx.Gtk3.UI.Widgets.ProfileDialog.glade")) { } + + private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) + { + builder.Autoconnect(this); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + } + + private void OkToggle_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + bool validFileName = true; + + foreach (char invalidChar in System.IO.Path.GetInvalidFileNameChars()) + { + if (_profileEntry.Text.Contains(invalidChar)) + { + validFileName = false; + } + } + + if (validFileName && !string.IsNullOrEmpty(_profileEntry.Text)) + { + FileName = $"{_profileEntry.Text}.json"; + + Respond(ResponseType.Ok); + } + else + { + _errorMessage.Text = "The file name contains invalid characters. Please try again."; + } + } + + private void CancelToggle_Activated(object sender, EventArgs args) + { + Respond(ResponseType.Cancel); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade new file mode 100644 index 00000000..adaf6608 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="_profileDialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - Profile Dialog</property> + <property name="modal">True</property> + <property name="window_position">center</property> + <property name="default_width">400</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkToggleButton" id="OkToggle"> + <property name="label" translatable="yes">OK</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="toggled" handler="OkToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="CancelToggle"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="toggled" handler="CancelToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="margin_top">20</property> + <property name="margin_bottom">10</property> + <property name="label" translatable="yes">Enter a name for the new profile:</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_profileEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_left">20</property> + <property name="margin_right">20</property> + <property name="margin_top">20</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="_errorMessage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_left">20</property> + <property name="margin_right">10</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <attributes> + <attribute name="foreground" value="#ffff00000000"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs b/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs new file mode 100644 index 00000000..6470790e --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs @@ -0,0 +1,27 @@ +using Gtk; + +namespace Ryujinx.UI.Widgets +{ + public class RawInputToTextEntry : Entry + { + public void SendKeyPressEvent(object o, KeyPressEventArgs args) + { + base.OnKeyPressEvent(args.Event); + } + + public void SendKeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + base.OnKeyReleaseEvent(args.Event); + } + + public void SendButtonPressEvent(object o, ButtonPressEventArgs args) + { + base.OnButtonPressEvent(args.Event); + } + + public void SendButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + base.OnButtonReleaseEvent(args.Event); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs new file mode 100644 index 00000000..f0b55cd8 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs @@ -0,0 +1,123 @@ +using Gtk; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; + +namespace Ryujinx.UI.Widgets +{ + internal class UserErrorDialog : MessageDialog + { + private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; + private const int OkResponseId = 0; + private const int SetupGuideResponseId = 1; + + private readonly UserError _userError; + + private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null) + { + _userError = error; + + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; + + Response += UserErrorDialog_Response; + + SetSizeRequest(120, 50); + + AddButton("OK", OkResponseId); + + bool isInSetupGuide = IsCoveredBySetupGuide(error); + + if (isInSetupGuide) + { + AddButton("Open the Setup Guide", SetupGuideResponseId); + } + + string errorCode = GetErrorCode(error); + + SecondaryUseMarkup = true; + + Title = $"Ryujinx error ({errorCode})"; + Text = $"{errorCode}: {GetErrorTitle(error)}"; + SecondaryText = GetErrorDescription(error); + + if (isInSetupGuide) + { + SecondaryText += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>"; + } + } + + private static string GetErrorCode(UserError error) + { + return $"RYU-{(uint)error:X4}"; + } + + private static string GetErrorTitle(UserError error) + { + return error switch + { + UserError.NoKeys => "Keys not found", + UserError.NoFirmware => "Firmware not found", + UserError.FirmwareParsingFailed => "Firmware parsing error", + UserError.ApplicationNotFound => "Application not found", + UserError.Unknown => "Unknown error", + _ => "Undefined error", + }; + } + + private static string GetErrorDescription(UserError error) + { + return error switch + { + UserError.NoKeys => "Ryujinx was unable to find your 'prod.keys' file", + UserError.NoFirmware => "Ryujinx was unable to find any firmwares installed", + UserError.FirmwareParsingFailed => "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + UserError.ApplicationNotFound => "Ryujinx couldn't find a valid application at the given path.", + UserError.Unknown => "An unknown error occured!", + _ => "An undefined error occured! This shouldn't happen, please contact a dev!", + }; + } + + private static bool IsCoveredBySetupGuide(UserError error) + { + return error switch + { + UserError.NoKeys or + UserError.NoFirmware or + UserError.FirmwareParsingFailed => true, + _ => false, + }; + } + + private static string GetSetupGuideUrl(UserError error) + { + if (!IsCoveredBySetupGuide(error)) + { + return null; + } + + return error switch + { + UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", + UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", + _ => SetupGuideUrl, + }; + } + + private void UserErrorDialog_Response(object sender, ResponseArgs args) + { + int responseId = (int)args.ResponseId; + + if (responseId == SetupGuideResponseId) + { + OpenHelper.OpenUrl(GetSetupGuideUrl(_userError)); + } + + Dispose(); + } + + public static void CreateUserErrorDialog(UserError error) + { + new UserErrorDialog(error).Run(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs new file mode 100644 index 00000000..fd912ef9 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs @@ -0,0 +1,511 @@ +using Gtk; +using Pango; +using Ryujinx.UI.Common.Configuration; +using System.Reflection; + +namespace Ryujinx.UI.Windows +{ + public partial class AboutWindow : Window + { + private Box _mainBox; + private Box _leftBox; + private Box _logoBox; + private Image _ryujinxLogo; + private Box _logoTextBox; + private Label _ryujinxLabel; + private Label _ryujinxPhoneticLabel; + private EventBox _ryujinxLink; + private Label _ryujinxLinkLabel; + private Label _versionLabel; + private Label _disclaimerLabel; + private EventBox _amiiboApiLink; + private Label _amiiboApiLinkLabel; + private Box _socialBox; + private EventBox _patreonEventBox; + private Box _patreonBox; + private Image _patreonLogo; + private Label _patreonLabel; + private EventBox _githubEventBox; + private Box _githubBox; + private Image _githubLogo; + private Label _githubLabel; + private Box _discordBox; + private EventBox _discordEventBox; + private Image _discordLogo; + private Label _discordLabel; + private EventBox _twitterEventBox; + private Box _twitterBox; + private Image _twitterLogo; + private Label _twitterLabel; + private Separator _separator; + private Box _rightBox; + private Label _aboutLabel; + private Label _aboutDescriptionLabel; + private Label _createdByLabel; + private TextView _createdByText; + private EventBox _contributorsEventBox; + private Label _contributorsLinkLabel; + private Label _patreonNamesLabel; + private ScrolledWindow _patreonNamesScrolled; + private TextView _patreonNamesText; + private EventBox _changelogEventBox; + private Label _changelogLinkLabel; + + private void InitializeComponent() + { + + // + // AboutWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 800; + DefaultHeight = 450; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Horizontal, 0); + + // + // _leftBox + // + _leftBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginStart = 30, + MarginEnd = 0, + }; + + // + // _logoBox + // + _logoBox = new Box(Orientation.Horizontal, 0); + + // + // _ryujinxLogo + // + _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png", 100, 100)) + { + Margin = 10, + MarginStart = 15, + }; + + // + // _logoTextBox + // + _logoTextBox = new Box(Orientation.Vertical, 0); + + // + // _ryujinxLabel + // + _ryujinxLabel = new Label("Ryujinx") + { + MarginTop = 15, + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f)); + + // + // _ryujinxPhoneticLabel + // + _ryujinxPhoneticLabel = new Label("(REE-YOU-JINX)") + { + Justify = Justification.Center, + }; + + // + // _ryujinxLink + // + _ryujinxLink = new EventBox() + { + Margin = 5 + }; + _ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed; + + // + // _ryujinxLinkLabel + // + _ryujinxLinkLabel = new Label("www.ryujinx.org") + { + TooltipText = "Click to open the Ryujinx website in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _versionLabel + // + _versionLabel = new Label(Program.Version) + { + Expand = true, + Justify = Justification.Center, + Margin = 5, + }; + + // + // _changelogEventBox + // + _changelogEventBox = new EventBox(); + _changelogEventBox.ButtonPressEvent += ChangelogButton_Pressed; + + // + // _changelogLinkLabel + // + _changelogLinkLabel = new Label("View Changelog on GitHub") + { + TooltipText = "Click to open the changelog for this version in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _changelogLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _disclaimerLabel + // + _disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.") + { + Expand = true, + Justify = Justification.Center, + Margin = 5, + Attributes = new AttrList(), + }; + _disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f)); + + // + // _amiiboApiLink + // + _amiiboApiLink = new EventBox() + { + Margin = 5, + }; + _amiiboApiLink.ButtonPressEvent += AmiiboApiButton_Pressed; + + // + // _amiiboApiLinkLabel + // + _amiiboApiLinkLabel = new Label("AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.") + { + TooltipText = "Click to open the AmiiboAPI website in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _amiiboApiLinkLabel.Attributes.Insert(new Pango.AttrScale(0.9f)); + + // + // _socialBox + // + _socialBox = new Box(Orientation.Horizontal, 0) + { + Margin = 25, + MarginBottom = 10, + }; + + // + // _patreonEventBox + // + _patreonEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Patreon page in your default browser.", + }; + _patreonEventBox.ButtonPressEvent += PatreonButton_Pressed; + + // + // _patreonBox + // + _patreonBox = new Box(Orientation.Vertical, 0); + + // + // _patreonLogo + // + _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _patreonLabel + // + _patreonLabel = new Label("Patreon") + { + Justify = Justification.Center, + }; + + // + // _githubEventBox + // + _githubEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx GitHub page in your default browser.", + }; + _githubEventBox.ButtonPressEvent += GitHubButton_Pressed; + + // + // _githubBox + // + _githubBox = new Box(Orientation.Vertical, 0); + + // + // _githubLogo + // + _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _githubLabel + // + _githubLabel = new Label("GitHub") + { + Justify = Justification.Center, + }; + + // + // _discordBox + // + _discordBox = new Box(Orientation.Vertical, 0); + + // + // _discordEventBox + // + _discordEventBox = new EventBox() + { + TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser.", + }; + _discordEventBox.ButtonPressEvent += DiscordButton_Pressed; + + // + // _discordLogo + // + _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Discord_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _discordLabel + // + _discordLabel = new Label("Discord") + { + Justify = Justification.Center, + }; + + // + // _twitterEventBox + // + _twitterEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Twitter page in your default browser.", + }; + _twitterEventBox.ButtonPressEvent += TwitterButton_Pressed; + + // + // _twitterBox + // + _twitterBox = new Box(Orientation.Vertical, 0); + + // + // _twitterLogo + // + _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _twitterLabel + // + _twitterLabel = new Label("Twitter") + { + Justify = Justification.Center, + }; + + // + // _separator + // + _separator = new Separator(Orientation.Vertical) + { + Margin = 15, + }; + + // + // _rightBox + // + _rightBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginTop = 40, + }; + + // + // _aboutLabel + // + _aboutLabel = new Label("About :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _aboutDescriptionLabel + // + _aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" + + "Please support us on Patreon.\n" + + "Get all the latest news on our Twitter or Discord.\n" + + "Developers interested in contributing can find out more on our GitHub or Discord.") + { + Margin = 15, + Halign = Align.Start, + }; + + // + // _createdByLabel + // + _createdByLabel = new Label("Maintained by :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _createdByText + // + _createdByText = new TextView() + { + WrapMode = Gtk.WrapMode.Word, + Editable = false, + CursorVisible = false, + Margin = 15, + MarginEnd = 30, + }; + _createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more..."; + + // + // _contributorsEventBox + // + _contributorsEventBox = new EventBox(); + _contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed; + + // + // _contributorsLinkLabel + // + _contributorsLinkLabel = new Label("See All Contributors...") + { + TooltipText = "Click to open the Contributors page in your default browser.", + MarginEnd = 30, + Halign = Align.End, + Attributes = new AttrList(), + }; + _contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesLabel + // + _patreonNamesLabel = new Label("Supported on Patreon by :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesScrolled + // + _patreonNamesScrolled = new ScrolledWindow() + { + Margin = 15, + MarginEnd = 30, + Expand = true, + ShadowType = ShadowType.In, + }; + _patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic); + + // + // _patreonNamesText + // + _patreonNamesText = new TextView() + { + WrapMode = Gtk.WrapMode.Word, + }; + _patreonNamesText.Buffer.Text = "Loading..."; + _patreonNamesText.SetProperty("editable", new GLib.Value(false)); + + ShowComponent(); + } + + private void ShowComponent() + { + _logoBox.Add(_ryujinxLogo); + + _ryujinxLink.Add(_ryujinxLinkLabel); + + _logoTextBox.Add(_ryujinxLabel); + _logoTextBox.Add(_ryujinxPhoneticLabel); + _logoTextBox.Add(_ryujinxLink); + + _logoBox.Add(_logoTextBox); + + _amiiboApiLink.Add(_amiiboApiLinkLabel); + + _patreonBox.Add(_patreonLogo); + _patreonBox.Add(_patreonLabel); + _patreonEventBox.Add(_patreonBox); + + _githubBox.Add(_githubLogo); + _githubBox.Add(_githubLabel); + _githubEventBox.Add(_githubBox); + + _discordBox.Add(_discordLogo); + _discordBox.Add(_discordLabel); + _discordEventBox.Add(_discordBox); + + _twitterBox.Add(_twitterLogo); + _twitterBox.Add(_twitterLabel); + _twitterEventBox.Add(_twitterBox); + + _socialBox.Add(_patreonEventBox); + _socialBox.Add(_githubEventBox); + _socialBox.Add(_discordEventBox); + _socialBox.Add(_twitterEventBox); + + _changelogEventBox.Add(_changelogLinkLabel); + + _leftBox.Add(_logoBox); + _leftBox.Add(_versionLabel); + _leftBox.Add(_changelogEventBox); + _leftBox.Add(_disclaimerLabel); + _leftBox.Add(_amiiboApiLink); + _leftBox.Add(_socialBox); + + _contributorsEventBox.Add(_contributorsLinkLabel); + _patreonNamesScrolled.Add(_patreonNamesText); + + _rightBox.Add(_aboutLabel); + _rightBox.Add(_aboutDescriptionLabel); + _rightBox.Add(_createdByLabel); + _rightBox.Add(_createdByText); + _rightBox.Add(_contributorsEventBox); + _rightBox.Add(_patreonNamesLabel); + _rightBox.Add(_patreonNamesScrolled); + + _mainBox.Add(_leftBox); + _mainBox.Add(_separator); + _mainBox.Add(_rightBox); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs new file mode 100644 index 00000000..f4bb533c --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs @@ -0,0 +1,85 @@ +using Gtk; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ryujinx.UI.Windows +{ + public partial class AboutWindow : Window + { + public AboutWindow() : base($"Ryujinx {Program.Version} - About") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(OpenHelper)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + InitializeComponent(); + + _ = DownloadPatronsJson(); + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + _patreonNamesText.Buffer.Text = "Connection Error."; + } + + HttpClient httpClient = new(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)); + } + catch + { + _patreonNamesText.Buffer.Text = "API Error."; + } + } + + // + // Events + // + private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://ryujinx.org"); + } + + private void AmiiboApiButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://amiiboapi.com"); + } + + private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://www.patreon.com/ryujinx"); + } + + private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx"); + } + + private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc"); + } + + private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu"); + } + + private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); + } + + private void ChangelogButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs new file mode 100644 index 00000000..3bf73318 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs @@ -0,0 +1,190 @@ +using Gtk; + +namespace Ryujinx.UI.Windows +{ + public partial class AmiiboWindow : Window + { + private Box _mainBox; + private ButtonBox _buttonBox; + private Button _scanButton; + private Button _cancelButton; + private CheckButton _randomUuidCheckBox; + private Box _amiiboBox; + private Box _amiiboHeadBox; + private Box _amiiboSeriesBox; + private Label _amiiboSeriesLabel; + private ComboBoxText _amiiboSeriesComboBox; + private Box _amiiboCharsBox; + private Label _amiiboCharsLabel; + private ComboBoxText _amiiboCharsComboBox; + private CheckButton _showAllCheckBox; + private Image _amiiboImage; + private Label _gameUsageLabel; + + private void InitializeComponent() + { + // + // AmiiboWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 600; + DefaultHeight = 470; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Vertical, 2); + + // + // _buttonBox + // + _buttonBox = new ButtonBox(Orientation.Horizontal) + { + Margin = 20, + LayoutStyle = ButtonBoxStyle.End, + }; + + // + // _scanButton + // + _scanButton = new Button() + { + Label = "Scan It!", + CanFocus = true, + ReceivesDefault = true, + MarginStart = 10, + }; + _scanButton.Clicked += ScanButton_Pressed; + + // + // _randomUuidCheckBox + // + _randomUuidCheckBox = new CheckButton() + { + Label = "Hack: Use Random Tag Uuid", + TooltipText = "This allows multiple scans of a single Amiibo.\n(used in The Legend of Zelda: Breath of the Wild)", + }; + + // + // _cancelButton + // + _cancelButton = new Button() + { + Label = "Cancel", + CanFocus = true, + ReceivesDefault = true, + MarginStart = 10, + }; + _cancelButton.Clicked += CancelButton_Pressed; + + // + // _amiiboBox + // + _amiiboBox = new Box(Orientation.Vertical, 0); + + // + // _amiiboHeadBox + // + _amiiboHeadBox = new Box(Orientation.Horizontal, 0) + { + Margin = 20, + Hexpand = true, + }; + + // + // _amiiboSeriesBox + // + _amiiboSeriesBox = new Box(Orientation.Horizontal, 0) + { + Hexpand = true, + }; + + // + // _amiiboSeriesLabel + // + _amiiboSeriesLabel = new Label("Amiibo Series:"); + + // + // _amiiboSeriesComboBox + // + _amiiboSeriesComboBox = new ComboBoxText(); + + // + // _amiiboCharsBox + // + _amiiboCharsBox = new Box(Orientation.Horizontal, 0) + { + Hexpand = true, + }; + + // + // _amiiboCharsLabel + // + _amiiboCharsLabel = new Label("Character:"); + + // + // _amiiboCharsComboBox + // + _amiiboCharsComboBox = new ComboBoxText(); + + // + // _showAllCheckBox + // + _showAllCheckBox = new CheckButton() + { + Label = "Show All Amiibo", + }; + + // + // _amiiboImage + // + _amiiboImage = new Image() + { + HeightRequest = 350, + WidthRequest = 350, + }; + + // + // _gameUsageLabel + // + _gameUsageLabel = new Label("") + { + MarginTop = 20, + }; + + ShowComponent(); + } + + private void ShowComponent() + { + _buttonBox.Add(_showAllCheckBox); + _buttonBox.Add(_randomUuidCheckBox); + _buttonBox.Add(_scanButton); + _buttonBox.Add(_cancelButton); + + _amiiboSeriesBox.Add(_amiiboSeriesLabel); + _amiiboSeriesBox.Add(_amiiboSeriesComboBox); + + _amiiboCharsBox.Add(_amiiboCharsLabel); + _amiiboCharsBox.Add(_amiiboCharsComboBox); + + _amiiboHeadBox.Add(_amiiboSeriesBox); + _amiiboHeadBox.Add(_amiiboCharsBox); + + _amiiboBox.PackStart(_amiiboHeadBox, true, true, 0); + _amiiboBox.PackEnd(_gameUsageLabel, false, false, 0); + _amiiboBox.PackEnd(_amiiboImage, false, false, 0); + + _mainBox.Add(_amiiboBox); + _mainBox.PackEnd(_buttonBox, false, false, 0); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs new file mode 100644 index 00000000..d8c0b0c0 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs @@ -0,0 +1,438 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Models.Amiibo; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Window = Gtk.Window; + +namespace Ryujinx.UI.Windows +{ + public partial class AmiiboWindow : Window + { + private const string DefaultJson = "{ \"amiibo\": [] }"; + + public string AmiiboId { get; private set; } + + public int DeviceId { get; set; } + public string TitleId { get; set; } + public string LastScannedAmiiboId { get; set; } + public bool LastScannedAmiiboShowAll { get; set; } + + public ResponseType Response { get; private set; } + + public bool UseRandomUuid + { + get + { + return _randomUuidCheckBox.Active; + } + } + + private readonly HttpClient _httpClient; + private readonly string _amiiboJsonPath; + + private readonly byte[] _amiiboLogoBytes; + + private List<AmiiboApi> _amiiboList; + + private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") + { + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + InitializeComponent(); + + _httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30), + }; + + Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + _amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); + _amiiboList = new List<AmiiboApi>(); + + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + _scanButton.Sensitive = false; + _randomUuidCheckBox.Sensitive = false; + + _ = LoadContentAsync(); + } + + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + + try + { + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + + return true; + } + catch (JsonException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + } + + private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson() + { + bool localIsValid = false; + bool remoteIsValid = false; + AmiiboJson amiiboJson = new(); + + try + { + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } + + if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) + { + remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); + } + } + catch (Exception exception) + { + if (!(localIsValid || remoteIsValid)) + { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + + // Neither local or remote files are valid JSON, close window. + ShowInfoDialog(); + Close(); + } + else if (!remoteIsValid) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + + // Only the local file is valid, the local one should be used + // but the user should be warned. + ShowInfoDialog(); + } + } + + return amiiboJson; + } + + private async Task LoadContentAsync() + { + AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); + + _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); + + if (LastScannedAmiiboShowAll) + { + _showAllCheckBox.Click(); + } + + ParseAmiiboData(); + + _showAllCheckBox.Clicked += ShowAllCheckBox_Clicked; + } + + private void ParseAmiiboData() + { + List<string> comboxItemList = new(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (!comboxItemList.Contains(_amiiboList[i].AmiiboSeries)) + { + if (!_showAllCheckBox.Active) + { + foreach (var game in _amiiboList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + comboxItemList.Add(_amiiboList[i].AmiiboSeries); + _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); + + break; + } + } + } + } + else + { + comboxItemList.Add(_amiiboList[i].AmiiboSeries); + _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); + } + } + } + + _amiiboSeriesComboBox.Changed += SeriesComboBox_Changed; + _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; + + if (LastScannedAmiiboId != "") + { + SelectLastScannedAmiibo(); + } + else + { + _amiiboSeriesComboBox.Active = 0; + } + } + + private void SelectLastScannedAmiibo() + { + bool isSet = _amiiboSeriesComboBox.SetActiveId(_amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == LastScannedAmiiboId).AmiiboSeries); + isSet = _amiiboCharsComboBox.SetActiveId(LastScannedAmiiboId); + + if (isSet == false) + { + _amiiboSeriesComboBox.Active = 0; + } + } + + private async Task<bool> NeedsUpdate(DateTime oldLastModified) + { + try + { + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); + } + + return false; + } + + private async Task<string> DownloadAmiiboJson() + { + try + { + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); + + if (response.IsSuccessStatusCode) + { + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; + } + + Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); + } + + GtkDialog.CreateInfoDialog("Amiibo API", "An error occured while fetching information from the API."); + + return null; + } + + private async Task UpdateAmiiboPreview(string imageUrl) + { + HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); + + if (response.IsSuccessStatusCode) + { + byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); + Pixbuf amiiboPreview = new(amiiboPreviewBytes); + + float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width, + (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height); + + int resizeHeight = (int)(amiiboPreview.Height * ratio); + int resizeWidth = (int)(amiiboPreview.Width * ratio); + + _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, InterpType.Bilinear); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}"); + } + } + + private static void ShowInfoDialog() + { + GtkDialog.CreateInfoDialog("Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online."); + } + + // + // Events + // + private void SeriesComboBox_Changed(object sender, EventArgs args) + { + _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; + + _amiiboCharsComboBox.RemoveAll(); + + List<AmiiboApi> amiiboSortedList = _amiiboList.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeriesComboBox.ActiveId).OrderBy(amiibo => amiibo.Name).ToList(); + + List<string> comboxItemList = new(); + + for (int i = 0; i < amiiboSortedList.Count; i++) + { + if (!comboxItemList.Contains(amiiboSortedList[i].Head + amiiboSortedList[i].Tail)) + { + if (!_showAllCheckBox.Active) + { + foreach (var game in amiiboSortedList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); + _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); + + break; + } + } + } + } + else + { + comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); + _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); + } + } + } + + _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; + + _amiiboCharsComboBox.Active = 0; + + _scanButton.Sensitive = true; + _randomUuidCheckBox.Sensitive = true; + } + + private void CharacterComboBox_Changed(object sender, EventArgs args) + { + AmiiboId = _amiiboCharsComboBox.ActiveId; + + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + string imageUrl = _amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image; + + var usageStringBuilder = new StringBuilder(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (_amiiboList[i].Head + _amiiboList[i].Tail == _amiiboCharsComboBox.ActiveId) + { + bool writable = false; + + foreach (var item in _amiiboList[i].GamesSwitch) + { + if (item.GameId.Contains(TitleId)) + { + foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) + { + usageStringBuilder.Append(Environment.NewLine); + usageStringBuilder.Append($"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); + + writable = usageItem.Write; + } + } + } + + if (usageStringBuilder.Length == 0) + { + usageStringBuilder.Append("Unknown."); + } + + _gameUsageLabel.Text = $"Usage{(writable ? " (Writable)" : "")} : {usageStringBuilder}"; + } + } + + _ = UpdateAmiiboPreview(imageUrl); + } + + private void ShowAllCheckBox_Clicked(object sender, EventArgs e) + { + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed; + _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; + + _amiiboSeriesComboBox.RemoveAll(); + _amiiboCharsComboBox.RemoveAll(); + + _scanButton.Sensitive = false; + _randomUuidCheckBox.Sensitive = false; + + new Task(ParseAmiiboData).Start(); + } + + private void ScanButton_Pressed(object sender, EventArgs args) + { + LastScannedAmiiboShowAll = _showAllCheckBox.Active; + + Response = ResponseType.Ok; + + Close(); + } + + private void CancelButton_Pressed(object sender, EventArgs args) + { + AmiiboId = ""; + LastScannedAmiiboId = ""; + LastScannedAmiiboShowAll = false; + + Response = ResponseType.Cancel; + + Close(); + } + + protected override void Dispose(bool disposing) + { + _httpClient.Dispose(); + + base.Dispose(disposing); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs new file mode 100644 index 00000000..7cddc362 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs @@ -0,0 +1,291 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.Common.Configuration; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Image = SixLabors.ImageSharp.Image; + +namespace Ryujinx.UI.Windows +{ + public class AvatarWindow : Window + { + public byte[] SelectedProfileImage; + public bool NewUser; + + private static readonly Dictionary<string, byte[]> _avatarDict = new(); + + private readonly ListStore _listStore; + private readonly IconView _iconView; + private readonly Button _setBackgroungColorButton; + private Gdk.RGBA _backgroundColor; + + public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + CanFocus = false; + Resizable = false; + Modal = true; + TypeHint = Gdk.WindowTypeHint.Dialog; + + SetDefaultSize(740, 400); + SetPosition(WindowPosition.Center); + + Box vbox = new(Orientation.Vertical, 0); + Add(vbox); + + ScrolledWindow scrolledWindow = new() + { + ShadowType = ShadowType.EtchedIn, + }; + scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic); + + Box hbox = new(Orientation.Horizontal, 0); + + Button chooseButton = new() + { + Label = "Choose", + CanFocus = true, + ReceivesDefault = true, + }; + chooseButton.Clicked += ChooseButton_Pressed; + + _setBackgroungColorButton = new Button() + { + Label = "Set Background Color", + CanFocus = true, + }; + _setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed; + + _backgroundColor.Red = 1; + _backgroundColor.Green = 1; + _backgroundColor.Blue = 1; + _backgroundColor.Alpha = 1; + + Button closeButton = new() + { + Label = "Close", + CanFocus = true, + }; + closeButton.Clicked += CloseButton_Pressed; + + vbox.PackStart(scrolledWindow, true, true, 0); + hbox.PackStart(chooseButton, true, true, 0); + hbox.PackStart(_setBackgroungColorButton, true, true, 0); + hbox.PackStart(closeButton, true, true, 0); + vbox.PackStart(hbox, false, false, 0); + + _listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf)); + _listStore.SetSortColumnId(0, SortType.Ascending); + + _iconView = new IconView(_listStore) + { + ItemWidth = 64, + ItemPadding = 10, + PixbufColumn = 1, + }; + + _iconView.SelectionChanged += IconView_SelectionChanged; + + scrolledWindow.Add(_iconView); + + _iconView.GrabFocus(); + + ProcessAvatars(); + + ShowAll(); + } + + public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) + { + if (_avatarDict.Count > 0) + { + return; + } + + string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); + string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(avatarPath)) + { + using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); + + Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + foreach (var item in romfs.EnumerateEntries()) + { + // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. + + if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) + { + using var file = new UniqueRef<IFile>(); + + romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using MemoryStream streamPng = MemoryStreamManager.Shared.GetStream(); + file.Get.AsStream().CopyTo(stream); + + stream.Position = 0; + + Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); + + avatarImage.SaveAsPng(streamPng); + + _avatarDict.Add(item.FullPath, streamPng.ToArray()); + } + } + } + } + + private void ProcessAvatars() + { + _listStore.Clear(); + + foreach (var avatar in _avatarDict) + { + _listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96)); + } + + _iconView.SelectPath(new TreePath(new[] { 0 })); + } + + private byte[] ProcessImage(byte[] data) + { + using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); + + Image avatarImage = Image.Load(data, new PngDecoder()); + + avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( + (byte)(_backgroundColor.Red * 255), + (byte)(_backgroundColor.Green * 255), + (byte)(_backgroundColor.Blue * 255), + (byte)(_backgroundColor.Alpha * 255) + ))); + avatarImage.SaveAsJpeg(streamJpg); + + return streamJpg.ToArray(); + } + + private void CloseButton_Pressed(object sender, EventArgs e) + { + SelectedProfileImage = null; + + Close(); + } + + private void IconView_SelectionChanged(object sender, EventArgs e) + { + if (_iconView.SelectedItems.Length > 0) + { + _listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]); + + SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]); + } + } + + private void SetBackgroungColorButton_Pressed(object sender, EventArgs e) + { + using ColorChooserDialog colorChooserDialog = new("Set Background Color", this); + + colorChooserDialog.UseAlpha = false; + colorChooserDialog.Rgba = _backgroundColor; + + if (colorChooserDialog.Run() == (int)ResponseType.Ok) + { + _backgroundColor = colorChooserDialog.Rgba; + + ProcessAvatars(); + } + + colorChooserDialog.Hide(); + } + + private void ChooseButton_Pressed(object sender, EventArgs e) + { + Close(); + } + + private static byte[] DecompressYaz0(Stream stream) + { + using BinaryReader reader = new(stream); + + reader.ReadInt32(); // Magic + + uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); + + reader.ReadInt64(); // Padding + + byte[] input = new byte[stream.Length - stream.Position]; + stream.Read(input, 0, input.Length); + + long inputOffset = 0; + + byte[] output = new byte[decodedLength]; + long outputOffset = 0; + + ushort mask = 0; + byte header = 0; + + while (outputOffset < decodedLength) + { + if ((mask >>= 1) == 0) + { + header = input[inputOffset++]; + mask = 0x80; + } + + if ((header & mask) > 0) + { + if (outputOffset == output.Length) + { + break; + } + + output[outputOffset++] = input[inputOffset++]; + } + else + { + byte byte1 = input[inputOffset++]; + byte byte2 = input[inputOffset++]; + + int dist = ((byte1 & 0xF) << 8) | byte2; + int position = (int)outputOffset - (dist + 1); + + int length = byte1 >> 4; + if (length == 0) + { + length = input[inputOffset++] + 0x12; + } + else + { + length += 2; + } + + while (length-- > 0) + { + output[outputOffset++] = output[position++]; + } + } + } + + return output; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs new file mode 100644 index 00000000..96ed0723 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs @@ -0,0 +1,156 @@ +using Gtk; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class CheatWindow : Window + { + private readonly string _enabledCheatsPath; + private readonly bool _noCheatsFound; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] TextView _buildIdTextView; + [GUI] TreeView _cheatTreeView; + [GUI] Button _saveButton; +#pragma warning restore CS0649, IDE0044 + + public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Gtk3.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } + + private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) + { + builder.Autoconnect(this); + _baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]"; + _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}"; + + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16")); + + _enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + _cheatTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string), typeof(string)); + + CellRendererToggle enableToggle = new(); + enableToggle.Toggled += (sender, args) => + { + _cheatTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + bool newValue = !(bool)_cheatTreeView.Model.GetValue(treeIter, 0); + _cheatTreeView.Model.SetValue(treeIter, 0, newValue); + + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) + { + do + { + _cheatTreeView.Model.SetValue(childIter, 0, newValue); + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + }; + + _cheatTreeView.AppendColumn("Enabled", enableToggle, "active", 0); + _cheatTreeView.AppendColumn("Name", new CellRendererText(), "text", 1); + _cheatTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); + + var buildIdColumn = _cheatTreeView.AppendColumn("Build Id", new CellRendererText(), "text", 3); + buildIdColumn.Visible = false; + + string[] enabled = Array.Empty<string>(); + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(System.IO.Path.Combine(modsBasePath, "contents")), titleId); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + TreeIter parentIter = default; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + string parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = System.IO.Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); + parentIter = ((TreeStore)_cheatTreeView.Model).AppendValues(false, buildId, parentPath, ""); + } + + string cleanName = cheat.Name[1..^7]; + ((TreeStore)_cheatTreeView.Model).AppendValues(parentIter, enabled.Contains($"{buildId}-{cheat.Name}"), cleanName, "", buildId); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + ((TreeStore)_cheatTreeView.Model).AppendValues(false, "No Cheats Found", "", ""); + _cheatTreeView.GetColumn(0).Visible = false; + + _noCheatsFound = true; + + _saveButton.Visible = false; + } + + _cheatTreeView.ExpandAll(); + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + if (_noCheatsFound) + { + return; + } + + List<string> enabledCheats = new(); + + if (_cheatTreeView.Model.GetIterFirst(out TreeIter parentIter)) + { + do + { + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) + { + do + { + var enabled = (bool)_cheatTreeView.Model.GetValue(childIter, 0); + + if (enabled) + { + var name = _cheatTreeView.Model.GetValue(childIter, 1).ToString(); + var buildId = _cheatTreeView.Model.GetValue(childIter, 3).ToString(); + + enabledCheats.Add($"{buildId}-<{name} Cheat>"); + } + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + } + while (_cheatTreeView.Model.IterNext(ref parentIter)); + } + + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade new file mode 100644 index 00000000..9a165f1a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade @@ -0,0 +1,150 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.21.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkWindow" id="_cheatWindow"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - Cheat Manager</property> + <property name="default_width">440</property> + <property name="default_height">550</property> + <child> + <object class="GtkBox" id="MainBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CheatBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="_baseTitleInfoLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="label" translatable="yes">Available Cheats</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTextView" id="_buildIdTextView"> + <property name="visible">True</property> + <property name="margin_top">10</property> + <property name="halign">center</property> + <property name="margin_bottom">10</property> + <property name="editable">False</property> + <property name="cursor_visible">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkTreeView" id="_cheatTreeView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="_saveButton"> + <property name="label" translatable="yes">Save</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_cancelButton"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="titlebar"> + <placeholder/> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs new file mode 100644 index 00000000..6712657f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs @@ -0,0 +1,1230 @@ +using Gtk; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using Ryujinx.Input.GTK3; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Threading; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using GUI = Gtk.Builder.ObjectAttribute; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.UI.Windows +{ + public class ControllerWindow : Window + { + private readonly PlayerIndex _playerIndex; + private readonly InputConfig _inputConfig; + + private bool _isWaitingForInput; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Adjustment _controllerStrongRumble; + [GUI] Adjustment _controllerWeakRumble; + [GUI] Adjustment _controllerDeadzoneLeft; + [GUI] Adjustment _controllerDeadzoneRight; + [GUI] Adjustment _controllerRangeLeft; + [GUI] Adjustment _controllerRangeRight; + [GUI] Adjustment _controllerTriggerThreshold; + [GUI] Adjustment _slotNumber; + [GUI] Adjustment _altSlotNumber; + [GUI] Adjustment _sensitivity; + [GUI] Adjustment _gyroDeadzone; + [GUI] CheckButton _enableMotion; + [GUI] CheckButton _enableCemuHook; + [GUI] CheckButton _mirrorInput; + [GUI] Entry _dsuServerHost; + [GUI] Entry _dsuServerPort; + [GUI] ComboBoxText _inputDevice; + [GUI] ComboBoxText _profile; + [GUI] Box _settingsBox; + [GUI] Box _motionAltBox; + [GUI] Box _motionBox; + [GUI] Box _dsuServerHostBox; + [GUI] Box _dsuServerPortBox; + [GUI] Box _motionControllerSlot; + [GUI] Grid _leftStickKeyboard; + [GUI] Grid _leftStickController; + [GUI] Box _deadZoneLeftBox; + [GUI] Box _rangeLeftBox; + [GUI] Grid _rightStickKeyboard; + [GUI] Grid _rightStickController; + [GUI] Box _deadZoneRightBox; + [GUI] Box _rangeRightBox; + [GUI] Grid _leftSideTriggerBox; + [GUI] Grid _rightSideTriggerBox; + [GUI] Box _triggerThresholdBox; + [GUI] ComboBoxText _controllerType; + [GUI] ToggleButton _lStick; + [GUI] CheckButton _invertLStickX; + [GUI] CheckButton _invertLStickY; + [GUI] CheckButton _rotateL90CW; + [GUI] ToggleButton _lStickUp; + [GUI] ToggleButton _lStickDown; + [GUI] ToggleButton _lStickLeft; + [GUI] ToggleButton _lStickRight; + [GUI] ToggleButton _lStickButton; + [GUI] ToggleButton _dpadUp; + [GUI] ToggleButton _dpadDown; + [GUI] ToggleButton _dpadLeft; + [GUI] ToggleButton _dpadRight; + [GUI] ToggleButton _minus; + [GUI] ToggleButton _l; + [GUI] ToggleButton _zL; + [GUI] ToggleButton _rStick; + [GUI] CheckButton _invertRStickX; + [GUI] CheckButton _invertRStickY; + [GUI] CheckButton _rotateR90CW; + [GUI] ToggleButton _rStickUp; + [GUI] ToggleButton _rStickDown; + [GUI] ToggleButton _rStickLeft; + [GUI] ToggleButton _rStickRight; + [GUI] ToggleButton _rStickButton; + [GUI] ToggleButton _a; + [GUI] ToggleButton _b; + [GUI] ToggleButton _x; + [GUI] ToggleButton _y; + [GUI] ToggleButton _plus; + [GUI] ToggleButton _r; + [GUI] ToggleButton _zR; + [GUI] ToggleButton _lSl; + [GUI] ToggleButton _lSr; + [GUI] ToggleButton _rSl; + [GUI] ToggleButton _rSr; + [GUI] Image _controllerImage; + [GUI] CheckButton _enableRumble; + [GUI] Box _rumbleBox; +#pragma warning restore CS0649, IDE0044 + + private readonly MainWindow _mainWindow; + private readonly IGamepadDriver _gtk3KeyboardDriver; + private IGamepad _selectedGamepad; + private bool _mousePressed; + private bool _middleMousePressed; + + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { } + + private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) + { + _mainWindow = mainWindow; + _selectedGamepad = null; + + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + _gtk3KeyboardDriver = new GTK3KeyboardDriver(this); + + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + builder.Autoconnect(this); + + _playerIndex = controllerId; + _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); + + Title = $"Ryujinx - Controller Settings - {_playerIndex}"; + + if (_playerIndex == PlayerIndex.Handheld) + { + _controllerType.Append(ControllerType.Handheld.ToString(), "Handheld"); + _controllerType.Sensitive = false; + } + else + { + _controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller"); + _controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair"); + _controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left"); + _controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right"); + } + + _controllerType.Active = 0; // Set initial value to first in list. + + // Bind Events. + _lStick.Clicked += ButtonForStick_Pressed; + _lStickUp.Clicked += Button_Pressed; + _lStickDown.Clicked += Button_Pressed; + _lStickLeft.Clicked += Button_Pressed; + _lStickRight.Clicked += Button_Pressed; + _lStickButton.Clicked += Button_Pressed; + _dpadUp.Clicked += Button_Pressed; + _dpadDown.Clicked += Button_Pressed; + _dpadLeft.Clicked += Button_Pressed; + _dpadRight.Clicked += Button_Pressed; + _minus.Clicked += Button_Pressed; + _l.Clicked += Button_Pressed; + _zL.Clicked += Button_Pressed; + _lSl.Clicked += Button_Pressed; + _lSr.Clicked += Button_Pressed; + _rStick.Clicked += ButtonForStick_Pressed; + _rStickUp.Clicked += Button_Pressed; + _rStickDown.Clicked += Button_Pressed; + _rStickLeft.Clicked += Button_Pressed; + _rStickRight.Clicked += Button_Pressed; + _rStickButton.Clicked += Button_Pressed; + _a.Clicked += Button_Pressed; + _b.Clicked += Button_Pressed; + _x.Clicked += Button_Pressed; + _y.Clicked += Button_Pressed; + _plus.Clicked += Button_Pressed; + _r.Clicked += Button_Pressed; + _zR.Clicked += Button_Pressed; + _rSl.Clicked += Button_Pressed; + _rSr.Clicked += Button_Pressed; + _enableCemuHook.Clicked += CemuHookCheckButtonPressed; + + // Setup current values. + UpdateInputDeviceList(); + SetAvailableOptions(); + + ClearValues(); + if (_inputDevice.ActiveId != null) + { + SetCurrentValues(); + } + + mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + + _mainWindow.RendererWidget?.NpadManager.BlockInputUpdates(); + } + + private void CemuHookCheckButtonPressed(object sender, EventArgs e) + { + UpdateCemuHookSpecificFieldsVisibility(); + } + + private void HandleOnGamepadDisconnected(string id) + { + Application.Invoke(delegate + { + UpdateInputDeviceList(); + }); + } + + private void HandleOnGamepadConnected(string id) + { + Application.Invoke(delegate + { + UpdateInputDeviceList(); + }); + } + + protected override void OnDestroyed() + { + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + _mainWindow.RendererWidget?.NpadManager.UnblockInputUpdates(); + + _selectedGamepad?.Dispose(); + + _gtk3KeyboardDriver.Dispose(); + } + + private static string GetShortGamepadName(string str) + { + const string ShrinkChars = "..."; + const int MaxSize = 50; + + if (str.Length > MaxSize) + { + return $"{str.AsSpan(0, MaxSize - ShrinkChars.Length)}{ShrinkChars}"; + } + + return str; + } + + private void UpdateInputDeviceList() + { + _inputDevice.RemoveAll(); + _inputDevice.Append("disabled", "Disabled"); + _inputDevice.SetActiveId("disabled"); + + foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) + { + IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + + if (gamepad != null) + { + _inputDevice.Append($"keyboard/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); + + gamepad.Dispose(); + } + } + + foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) + { + IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + + if (gamepad != null) + { + _inputDevice.Append($"controller/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); + + gamepad.Dispose(); + } + } + + switch (_inputConfig) + { + case StandardKeyboardInputConfig keyboard: + _inputDevice.SetActiveId($"keyboard/{keyboard.Id}"); + break; + case StandardControllerInputConfig controller: + _inputDevice.SetActiveId($"controller/{controller.Id}"); + break; + } + } + + private void UpdateCemuHookSpecificFieldsVisibility() + { + if (_enableCemuHook.Active) + { + _dsuServerHostBox.Show(); + _dsuServerPortBox.Show(); + _motionControllerSlot.Show(); + _motionAltBox.Show(); + _mirrorInput.Show(); + } + else + { + _dsuServerHostBox.Hide(); + _dsuServerPortBox.Hide(); + _motionControllerSlot.Hide(); + _motionAltBox.Hide(); + _mirrorInput.Hide(); + } + } + + private void SetAvailableOptions() + { + if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) + { + ShowAll(); + _leftStickController.Hide(); + _rightStickController.Hide(); + _deadZoneLeftBox.Hide(); + _deadZoneRightBox.Hide(); + _rangeLeftBox.Hide(); + _rangeRightBox.Hide(); + _triggerThresholdBox.Hide(); + _motionBox.Hide(); + _rumbleBox.Hide(); + } + else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) + { + ShowAll(); + _leftStickKeyboard.Hide(); + _rightStickKeyboard.Hide(); + + UpdateCemuHookSpecificFieldsVisibility(); + } + else + { + _settingsBox.Hide(); + } + + ClearValues(); + } + + private void SetCurrentValues() + { + SetControllerSpecificFields(); + + SetProfiles(); + + if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig) + { + SetValues(_inputConfig); + } + else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig) + { + SetValues(_inputConfig); + } + } + + private void SetControllerSpecificFields() + { + _leftSideTriggerBox.Hide(); + _rightSideTriggerBox.Hide(); + _motionAltBox.Hide(); + + switch (_controllerType.ActiveId) + { + case "JoyconLeft": + _leftSideTriggerBox.Show(); + break; + case "JoyconRight": + _rightSideTriggerBox.Show(); + break; + case "JoyconPair": + _motionAltBox.Show(); + break; + } + + if (!OperatingSystem.IsMacOS()) + { + _controllerImage.Pixbuf = _controllerType.ActiveId switch + { + "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400), + "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500), + "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500), + _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500), + }; + } + } + + private void ClearValues() + { + _lStick.Label = "Unbound"; + _lStickUp.Label = "Unbound"; + _lStickDown.Label = "Unbound"; + _lStickLeft.Label = "Unbound"; + _lStickRight.Label = "Unbound"; + _lStickButton.Label = "Unbound"; + _dpadUp.Label = "Unbound"; + _dpadDown.Label = "Unbound"; + _dpadLeft.Label = "Unbound"; + _dpadRight.Label = "Unbound"; + _minus.Label = "Unbound"; + _l.Label = "Unbound"; + _zL.Label = "Unbound"; + _lSl.Label = "Unbound"; + _lSr.Label = "Unbound"; + _rStick.Label = "Unbound"; + _rStickUp.Label = "Unbound"; + _rStickDown.Label = "Unbound"; + _rStickLeft.Label = "Unbound"; + _rStickRight.Label = "Unbound"; + _rStickButton.Label = "Unbound"; + _a.Label = "Unbound"; + _b.Label = "Unbound"; + _x.Label = "Unbound"; + _y.Label = "Unbound"; + _plus.Label = "Unbound"; + _r.Label = "Unbound"; + _zR.Label = "Unbound"; + _rSl.Label = "Unbound"; + _rSr.Label = "Unbound"; + _controllerStrongRumble.Value = 1; + _controllerWeakRumble.Value = 1; + _controllerDeadzoneLeft.Value = 0; + _controllerDeadzoneRight.Value = 0; + _controllerRangeLeft.Value = 1; + _controllerRangeRight.Value = 1; + _controllerTriggerThreshold.Value = 0; + _mirrorInput.Active = false; + _enableMotion.Active = false; + _enableCemuHook.Active = false; + _slotNumber.Value = 0; + _altSlotNumber.Value = 0; + _sensitivity.Value = 100; + _gyroDeadzone.Value = 1; + _dsuServerHost.Buffer.Text = ""; + _dsuServerPort.Buffer.Text = ""; + _enableRumble.Active = false; + } + + private void SetValues(InputConfig config) + { + switch (config) + { + case StandardKeyboardInputConfig keyboardConfig: + if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString())) + { + _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld + ? ControllerType.Handheld.ToString() + : ControllerType.ProController.ToString()); + } + + _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString(); + _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString(); + _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString(); + _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString(); + _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString(); + _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString(); + _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString(); + _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString(); + _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString(); + _minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString(); + _l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString(); + _zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString(); + _lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString(); + _lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString(); + _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString(); + _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString(); + _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString(); + _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString(); + _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString(); + _a.Label = keyboardConfig.RightJoycon.ButtonA.ToString(); + _b.Label = keyboardConfig.RightJoycon.ButtonB.ToString(); + _x.Label = keyboardConfig.RightJoycon.ButtonX.ToString(); + _y.Label = keyboardConfig.RightJoycon.ButtonY.ToString(); + _plus.Label = keyboardConfig.RightJoycon.ButtonPlus.ToString(); + _r.Label = keyboardConfig.RightJoycon.ButtonR.ToString(); + _zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString(); + _rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString(); + _rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString(); + break; + + case StandardControllerInputConfig controllerConfig: + if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString())) + { + _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld + ? ControllerType.Handheld.ToString() + : ControllerType.ProController.ToString()); + } + + _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString(); + _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX; + _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY; + _rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW; + _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString(); + _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString(); + _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString(); + _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString(); + _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString(); + _minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString(); + _l.Label = controllerConfig.LeftJoycon.ButtonL.ToString(); + _zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString(); + _lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString(); + _lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString(); + _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString(); + _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX; + _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY; + _rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW; + _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString(); + _a.Label = controllerConfig.RightJoycon.ButtonA.ToString(); + _b.Label = controllerConfig.RightJoycon.ButtonB.ToString(); + _x.Label = controllerConfig.RightJoycon.ButtonX.ToString(); + _y.Label = controllerConfig.RightJoycon.ButtonY.ToString(); + _plus.Label = controllerConfig.RightJoycon.ButtonPlus.ToString(); + _r.Label = controllerConfig.RightJoycon.ButtonR.ToString(); + _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString(); + _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString(); + _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString(); + _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble; + _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble; + _enableRumble.Active = controllerConfig.Rumble.EnableRumble; + _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft; + _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight; + _controllerRangeLeft.Value = controllerConfig.RangeLeft; + _controllerRangeRight.Value = controllerConfig.RangeRight; + _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold; + _sensitivity.Value = controllerConfig.Motion.Sensitivity; + _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone; + _enableMotion.Active = controllerConfig.Motion.EnableMotion; + _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook; + + // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. + if (_controllerRangeLeft.Value <= 0.0 && _controllerRangeRight.Value <= 0.0) + { + _controllerRangeLeft.Value = 1.0; + _controllerRangeRight.Value = 1.0; + + Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); + } + + if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig) + { + _slotNumber.Value = cemuHookMotionConfig.Slot; + _altSlotNumber.Value = cemuHookMotionConfig.AltSlot; + _mirrorInput.Active = cemuHookMotionConfig.MirrorInput; + _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost; + _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString(); + } + + break; + } + } + + private InputConfig GetValues() + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { +#pragma warning disable CA1806, IDE0055 // Disable formatting + Enum.TryParse(_lStickUp.Label, out Key lStickUp); + Enum.TryParse(_lStickDown.Label, out Key lStickDown); + Enum.TryParse(_lStickLeft.Label, out Key lStickLeft); + Enum.TryParse(_lStickRight.Label, out Key lStickRight); + Enum.TryParse(_lStickButton.Label, out Key lStickButton); + Enum.TryParse(_dpadUp.Label, out Key lDPadUp); + Enum.TryParse(_dpadDown.Label, out Key lDPadDown); + Enum.TryParse(_dpadLeft.Label, out Key lDPadLeft); + Enum.TryParse(_dpadRight.Label, out Key lDPadRight); + Enum.TryParse(_minus.Label, out Key lButtonMinus); + Enum.TryParse(_l.Label, out Key lButtonL); + Enum.TryParse(_zL.Label, out Key lButtonZl); + Enum.TryParse(_lSl.Label, out Key lButtonSl); + Enum.TryParse(_lSr.Label, out Key lButtonSr); + + Enum.TryParse(_rStickUp.Label, out Key rStickUp); + Enum.TryParse(_rStickDown.Label, out Key rStickDown); + Enum.TryParse(_rStickLeft.Label, out Key rStickLeft); + Enum.TryParse(_rStickRight.Label, out Key rStickRight); + Enum.TryParse(_rStickButton.Label, out Key rStickButton); + Enum.TryParse(_a.Label, out Key rButtonA); + Enum.TryParse(_b.Label, out Key rButtonB); + Enum.TryParse(_x.Label, out Key rButtonX); + Enum.TryParse(_y.Label, out Key rButtonY); + Enum.TryParse(_plus.Label, out Key rButtonPlus); + Enum.TryParse(_r.Label, out Key rButtonR); + Enum.TryParse(_zR.Label, out Key rButtonZr); + Enum.TryParse(_rSl.Label, out Key rButtonSl); + Enum.TryParse(_rSr.Label, out Key rButtonSr); +#pragma warning restore CA1806, IDE0055 + + return new StandardKeyboardInputConfig + { + Backend = InputBackendType.WindowKeyboard, + Version = InputConfig.CurrentVersion, + Id = _inputDevice.ActiveId.Split("/")[1], + ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId), + PlayerIndex = _playerIndex, + LeftJoycon = new LeftJoyconCommonConfig<Key> + { + ButtonMinus = lButtonMinus, + ButtonL = lButtonL, + ButtonZl = lButtonZl, + ButtonSl = lButtonSl, + ButtonSr = lButtonSr, + DpadUp = lDPadUp, + DpadDown = lDPadDown, + DpadLeft = lDPadLeft, + DpadRight = lDPadRight, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick<Key> + { + StickUp = lStickUp, + StickDown = lStickDown, + StickLeft = lStickLeft, + StickRight = lStickRight, + StickButton = lStickButton, + }, + RightJoycon = new RightJoyconCommonConfig<Key> + { + ButtonA = rButtonA, + ButtonB = rButtonB, + ButtonX = rButtonX, + ButtonY = rButtonY, + ButtonPlus = rButtonPlus, + ButtonR = rButtonR, + ButtonZr = rButtonZr, + ButtonSl = rButtonSl, + ButtonSr = rButtonSr, + }, + RightJoyconStick = new JoyconConfigKeyboardStick<Key> + { + StickUp = rStickUp, + StickDown = rStickDown, + StickLeft = rStickLeft, + StickRight = rStickRight, + StickButton = rStickButton, + }, + }; + } + + if (_inputDevice.ActiveId.StartsWith("controller")) + { +#pragma warning disable CA1806, IDE0055 // Disable formatting + Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick); + Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton); + Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus); + Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL); + Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl); + Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl); + Enum.TryParse(_lSr.Label, out ConfigGamepadInputId lButtonSr); + Enum.TryParse(_dpadUp.Label, out ConfigGamepadInputId lDPadUp); + Enum.TryParse(_dpadDown.Label, out ConfigGamepadInputId lDPadDown); + Enum.TryParse(_dpadLeft.Label, out ConfigGamepadInputId lDPadLeft); + Enum.TryParse(_dpadRight.Label, out ConfigGamepadInputId lDPadRight); + + Enum.TryParse(_rStick.Label, out ConfigStickInputId rStick); + Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton); + Enum.TryParse(_a.Label, out ConfigGamepadInputId rButtonA); + Enum.TryParse(_b.Label, out ConfigGamepadInputId rButtonB); + Enum.TryParse(_x.Label, out ConfigGamepadInputId rButtonX); + Enum.TryParse(_y.Label, out ConfigGamepadInputId rButtonY); + Enum.TryParse(_plus.Label, out ConfigGamepadInputId rButtonPlus); + Enum.TryParse(_r.Label, out ConfigGamepadInputId rButtonR); + Enum.TryParse(_zR.Label, out ConfigGamepadInputId rButtonZr); + Enum.TryParse(_rSl.Label, out ConfigGamepadInputId rButtonSl); + Enum.TryParse(_rSr.Label, out ConfigGamepadInputId rButtonSr); + + int.TryParse(_dsuServerPort.Buffer.Text, out int port); +#pragma warning restore CA1806, IDE0055 + + MotionConfigController motionConfig; + + if (_enableCemuHook.Active) + { + motionConfig = new CemuHookMotionConfigController + { + MotionBackend = MotionInputBackendType.CemuHook, + EnableMotion = _enableMotion.Active, + Sensitivity = (int)_sensitivity.Value, + GyroDeadzone = _gyroDeadzone.Value, + MirrorInput = _mirrorInput.Active, + Slot = (int)_slotNumber.Value, + AltSlot = (int)_altSlotNumber.Value, + DsuServerHost = _dsuServerHost.Buffer.Text, + DsuServerPort = port, + }; + } + else + { + motionConfig = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = _enableMotion.Active, + Sensitivity = (int)_sensitivity.Value, + GyroDeadzone = _gyroDeadzone.Value, + }; + } + + return new StandardControllerInputConfig + { + Backend = InputBackendType.GamepadSDL2, + Version = InputConfig.CurrentVersion, + Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0], + ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId), + PlayerIndex = _playerIndex, + DeadzoneLeft = (float)_controllerDeadzoneLeft.Value, + DeadzoneRight = (float)_controllerDeadzoneRight.Value, + RangeLeft = (float)_controllerRangeLeft.Value, + RangeRight = (float)_controllerRangeRight.Value, + TriggerThreshold = (float)_controllerTriggerThreshold.Value, + LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> + { + ButtonMinus = lButtonMinus, + ButtonL = lButtonL, + ButtonZl = lButtonZl, + ButtonSl = lButtonSl, + ButtonSr = lButtonSr, + DpadUp = lDPadUp, + DpadDown = lDPadDown, + DpadLeft = lDPadLeft, + DpadRight = lDPadRight, + }, + LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + InvertStickX = _invertLStickX.Active, + Joystick = lStick, + InvertStickY = _invertLStickY.Active, + StickButton = lStickButton, + Rotate90CW = _rotateL90CW.Active, + }, + RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> + { + ButtonA = rButtonA, + ButtonB = rButtonB, + ButtonX = rButtonX, + ButtonY = rButtonY, + ButtonPlus = rButtonPlus, + ButtonR = rButtonR, + ButtonZr = rButtonZr, + ButtonSl = rButtonSl, + ButtonSr = rButtonSr, + }, + RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + InvertStickX = _invertRStickX.Active, + Joystick = rStick, + InvertStickY = _invertRStickY.Active, + StickButton = rStickButton, + Rotate90CW = _rotateR90CW.Active, + }, + Motion = motionConfig, + Rumble = new RumbleConfigController + { + StrongRumble = (float)_controllerStrongRumble.Value, + WeakRumble = (float)_controllerWeakRumble.Value, + EnableRumble = _enableRumble.Active, + }, + }; + } + + if (!_inputDevice.ActiveId.StartsWith("disabled")) + { + GtkDialog.CreateErrorDialog("Invalid data detected in one or more fields; the configuration was not saved."); + } + + return null; + } + + private string GetProfileBasePath() + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller"); + } + + return AppDataManager.ProfilesDirPath; + } + + // + // Events + // + private void InputDevice_Changed(object sender, EventArgs args) + { + SetAvailableOptions(); + SetControllerSpecificFields(); + + _selectedGamepad?.Dispose(); + _selectedGamepad = null; + + if (_inputDevice.ActiveId != null) + { + SetProfiles(); + + string id = GetCurrentGamepadId(); + + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + if (_inputConfig is StandardKeyboardInputConfig) + { + SetValues(_inputConfig); + } + + if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver) + { + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id); + } + else + { + _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + } + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + if (_inputConfig is StandardControllerInputConfig) + { + SetValues(_inputConfig); + } + + _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + } + } + } + + private string GetCurrentGamepadId() + { + if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled") + { + return null; + } + + return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0]; + } + + private void Controller_Changed(object sender, EventArgs args) + { + SetControllerSpecificFields(); + } + + private IButtonAssigner CreateButtonAssigner(bool forStick) + { + IButtonAssigner assigner; + + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad); + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick); + } + else + { + throw new Exception("Controller not supported"); + } + + return assigner; + } + + private void HandleButtonPressed(ToggleButton button, bool forStick) + { + if (_isWaitingForInput) + { + button.Active = false; + + return; + } + + _mousePressed = false; + + ButtonPressEvent += MouseClick; + + IButtonAssigner assigner = CreateButtonAssigner(forStick); + + _isWaitingForInput = true; + + // Open GTK3 keyboard for cancel operations + IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0"); + + Thread inputThread = new(() => + { + assigner.Initialize(); + + while (true) + { + Thread.Sleep(10); + assigner.ReadInput(); + + if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel()) + { + break; + } + } + + string pressedButton = assigner.GetPressedButton(); + + Application.Invoke(delegate + { + if (_middleMousePressed) + { + button.Label = "Unbound"; + } + else if (pressedButton != "") + { + button.Label = pressedButton; + } + + _middleMousePressed = false; + + ButtonPressEvent -= MouseClick; + keyboard.Dispose(); + + button.Active = false; + _isWaitingForInput = false; + }); + }) + { + Name = "GUI.InputThread", + IsBackground = true, + }; + inputThread.Start(); + } + + private void Button_Pressed(object sender, EventArgs args) + { + HandleButtonPressed((ToggleButton)sender, false); + } + + private void ButtonForStick_Pressed(object sender, EventArgs args) + { + HandleButtonPressed((ToggleButton)sender, true); + } + + private void MouseClick(object sender, ButtonPressEventArgs args) + { + _mousePressed = true; + _middleMousePressed = args.Event.Button == 2; + } + + private void SetProfiles() + { + _profile.RemoveAll(); + + string basePath = GetProfileBasePath(); + + if (!Directory.Exists(basePath)) + { + Directory.CreateDirectory(basePath); + } + + if (_inputDevice.ActiveId == null || _inputDevice.ActiveId.Equals("disabled")) + { + _profile.Append("default", "None"); + } + else + { + _profile.Append("default", "Default"); + + foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories)) + { + _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile)); + } + } + + _profile.SetActiveId("default"); + } + + private void ProfileLoad_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) + { + return; + } + + InputConfig config = null; + int pos = _profile.Active; + + if (_profile.ActiveId == "default") + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = null, + ControllerType = ControllerType.ProController, + LeftJoycon = new LeftJoyconCommonConfig<Key> + { + 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<Key> + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + + RightJoycon = new RightJoyconCommonConfig<Key> + { + 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<Key> + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }; + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo"); + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = null, + ControllerType = ControllerType.JoyconPair, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false, + }, + }; + } + } + else + { + string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); + + if (!File.Exists(path)) + { + if (pos >= 0) + { + _profile.Remove(pos); + } + + return; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); + } + catch (JsonException) { } + } + + SetValues(config); + } + + private void ProfileAdd_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled") + { + return; + } + + InputConfig inputConfig = GetValues(); + ProfileDialog profileDialog = new(); + + if (inputConfig == null) + { + return; + } + + if (profileDialog.Run() == (int)ResponseType.Ok) + { + string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); + string jsonString = JsonHelper.Serialize(inputConfig, _serializerContext.InputConfig); + + File.WriteAllText(path, jsonString); + } + + profileDialog.Dispose(); + + SetProfiles(); + } + + private void ProfileRemove_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) + { + return; + } + + MessageDialog confirmDialog = GtkDialog.CreateConfirmationDialog("Deleting Profile", "This action is irreversible, are you sure you want to continue?"); + + if (confirmDialog.Run() == (int)ResponseType.Yes) + { + string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); + + if (File.Exists(path)) + { + File.Delete(path); + } + + SetProfiles(); + } + } + + private void SaveToggle_Activated(object sender, EventArgs args) + { + InputConfig inputConfig = GetValues(); + + var newConfig = new List<InputConfig>(); + newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); + + if (_inputConfig == null && inputConfig != null) + { + newConfig.Add(inputConfig); + } + else + { + if (_inputDevice.ActiveId == "disabled") + { + newConfig.Remove(_inputConfig); + } + else if (inputConfig != null) + { + int index = newConfig.IndexOf(_inputConfig); + + newConfig[index] = inputConfig; + } + } + + _mainWindow.RendererWidget?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + // Atomically replace and signal input change. + // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. + ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + Dispose(); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade new file mode 100644 index 00000000..e433f5cc --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade @@ -0,0 +1,2241 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkAdjustment" id="_altSlotNumber"> + <property name="upper">4</property> + <property name="step_increment">1</property> + <property name="page_increment">4</property> + </object> + <object class="GtkAdjustment" id="_controllerStrongRumble"> + <property name="lower">0.1</property> + <property name="upper">10</property> + <property name="value">1.0</property> + <property name="step_increment">0.1</property> + <property name="page_increment">1.0</property> + </object> + <object class="GtkAdjustment" id="_controllerWeakRumble"> + <property name="lower">0.1</property> + <property name="upper">10</property> + <property name="value">1.0</property> + <property name="step_increment">0.1</property> + <property name="page_increment">1.0</property> + </object> + <object class="GtkAdjustment" id="_controllerDeadzoneLeft"> + <property name="upper">1</property> + <property name="value">0.050000000000000003</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_controllerDeadzoneRight"> + <property name="upper">1</property> + <property name="value">0.050000000000000003</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_controllerRangeLeft"> + <property name="upper">2</property> + <property name="value">1.000000000000000003</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_controllerRangeRight"> + <property name="upper">2</property> + <property name="value">1.000000000000000003</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_controllerTriggerThreshold"> + <property name="upper">1</property> + <property name="value">0.5</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_gyroDeadzone"> + <property name="upper">100</property> + <property name="value">0.01</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + <property name="page_size">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="_sensitivity"> + <property name="upper">1000</property> + <property name="value">100</property> + <property name="step_increment">1</property> + <property name="page_increment">4</property> + </object> + <object class="GtkAdjustment" id="_slotNumber"> + <property name="upper">4</property> + <property name="step_increment">1</property> + <property name="page_increment">4</property> + </object> + <object class="GtkWindow" id="_controllerWin"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - Controller Settings</property> + <property name="modal">True</property> + <property name="window_position">center</property> + <property name="default_width">1200</property> + <property name="default_height">720</property> + <child type="titlebar"> + <placeholder/> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="HeadBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <child> + <object class="GtkBox" id="DeviceBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">5</property> + <property name="label" translatable="yes">Input Device</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_inputDevice"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="active">0</property> + <property name="active_id">disabled</property> + <items> + <item id="disabled" translatable="yes">Disabled</item> + </items> + <signal name="changed" handler="InputDevice_Changed" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="ControllerTypeBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">20</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">The controller's type</property> + <property name="halign">center</property> + <property name="margin_right">5</property> + <property name="label" translatable="yes">Controller Type:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_controllerType"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">The controller's type</property> + <property name="active">0</property> + <signal name="changed" handler="Controller_Changed" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="ProfileBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">20</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">5</property> + <property name="label" translatable="yes">Profile:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_profile"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">5</property> + <property name="active">0</property> + <property name="active_id">default</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton"> + <property name="label" translatable="yes">Load</property> + <property name="width_request">60</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">5</property> + <signal name="toggled" handler="ProfileLoad_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton"> + <property name="label" translatable="yes">Add</property> + <property name="width_request">60</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">5</property> + <signal name="toggled" handler="ProfileAdd_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkToggleButton"> + <property name="label" translatable="yes">Remove</property> + <property name="width_request">60</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="toggled" handler="ProfileRemove_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_settingsBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_top">5</property> + <child> + <object class="GtkBox" id="ButtonsBox"> + <property name="width_request">156</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Buttons</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">A</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">B</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">X</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Y</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_a"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_b"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_x"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_y"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">+</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">-</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">5</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_minus"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_plus"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="LeftStickBox"> + <property name="width_request">160</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Left Stick</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">5</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick Button</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lStickButton"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_leftStickKeyboard"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkToggleButton" id="_lStickDown"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lStickUp"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lStickLeft"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lStickRight"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick Down</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick Up</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick Right</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick Left</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_leftStickController"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">LStick</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lStick"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_invertLStickX"> + <property name="label" translatable="yes">Invert Stick X</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_invertLStickY"> + <property name="label" translatable="yes">Invert Stick Y</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_rotateL90CW"> + <property name="label" translatable="yes">Rotate 90° Clockwise</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_deadZoneLeftBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Deadzone Left</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerDeadzoneLeft</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_rangeLeftBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Range Left</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerRangeLeft</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TriggerBox"> + <property name="width_request">150</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Triggers</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">L</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">R</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_l"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_r"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">ZL</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">ZR</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_zL"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_zR"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_leftSideTriggerBox"> + <property name="name">_sideTriggerBox</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Left SL</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Left SR</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lSl"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_lSr"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_rightSideTriggerBox"> + <property name="name">_sideTriggerBox</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Right SL</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Right SR</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rSl"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rSr"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_triggerThresholdBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_left">10</property> + <property name="label" translatable="yes">Trigger Threshold</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerTriggerThreshold</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <child> + <object class="GtkBox" id="DPadBox"> + <property name="width_request">156</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">10</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Directional Pad</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Dpad Up</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Dpad Down</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Dpad Left</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Dpad Right</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_dpadUp"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_dpadDown"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_dpadLeft"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_dpadRight"> + <property name="label" translatable="yes"> </property> + <property name="width_request">70</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_rumbleBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Rumble</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_enableRumble"> + <property name="label" translatable="yes">Enable</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_StrongMultiBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Strong rumble multiplier</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerStrongRumble</property> + <property name="round_digits">1</property> + <property name="digits">1</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_WeakMultiBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Weak rumble multiplier</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerWeakRumble</property> + <property name="round_digits">1</property> + <property name="digits">1</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="RightStickBox"> + <property name="width_request">160</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Right Stick</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">5</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick Button</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStickButton"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_rightStickKeyboard"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick Up</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick Down</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick Left</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick Right</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStickUp"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStickDown"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStickLeft"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStickRight"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="_rightStickController"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">3</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="width_request">80</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">RStick</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_rStick"> + <property name="label" translatable="yes"> </property> + <property name="width_request">65</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_invertRStickX"> + <property name="label" translatable="yes">Invert Stick X</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_invertRStickY"> + <property name="label" translatable="yes">Invert Stick Y</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_rotateR90CW"> + <property name="label" translatable="yes">Rotate 90° Clockwise</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_deadZoneRightBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Deadzone Right</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerDeadzoneRight</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_rangeRightBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Range Right</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_controllerRangeRight</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_motionBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="orientation">vertical</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="label" translatable="yes">Motion</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_enableMotion"> + <property name="label" translatable="yes">Enable Motion Controls</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_enableCemuHook"> + <property name="label" translatable="yes">Use CemuHook compatible motion</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_motionControllerSlot"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">17</property> + <property name="label" translatable="yes">Controller Slot</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_slot"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_left">10</property> + <property name="adjustment">_slotNumber</property> + <property name="climb_rate">1</property> + <property name="snap_to_ticks">True</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">5</property> + <property name="label" translatable="yes">Gyro Sensitivity %</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="text" translatable="yes">0</property> + <property name="adjustment">_sensitivity</property> + <property name="climb_rate">1</property> + <property name="snap_to_ticks">True</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_motionAltBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_mirrorInput"> + <property name="label" translatable="yes">Mirror Input</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Right JoyCon Slot</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_slotRight"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="text" translatable="yes">0</property> + <property name="adjustment">_altSlotNumber</property> + <property name="climb_rate">1</property> + <property name="snap_to_ticks">True</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_dsuServerHostBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">30</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Server Host</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_dsuServerHost"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_dsuServerPortBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">30</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Server Port</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_dsuServerPort"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">7</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Gyro Deadzone</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">8</property> + </packing> + </child> + <child> + <object class="GtkScale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">_gyroDeadzone</property> + <property name="round_digits">2</property> + <property name="digits">2</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">9</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkImage" id="_controllerImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">20</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_right">5</property> + <property name="margin_top">3</property> + <property name="margin_bottom">3</property> + <property name="layout_style">end</property> + <child> + <object class="GtkToggleButton" id="SaveToggle"> + <property name="label" translatable="yes">Save</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="toggled" handler="SaveToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="CloseToggle"> + <property name="label" translatable="yes">Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_left">4</property> + <signal name="toggled" handler="CloseToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs new file mode 100644 index 00000000..388f1108 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs @@ -0,0 +1,280 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class DlcWindow : Window + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly string _titleId; + private readonly string _dlcJsonPath; + private readonly List<DownloadableContentContainer> _dlcContainerList; + + private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] TreeView _dlcTreeView; + [GUI] TreeSelection _dlcTreeSelection; +#pragma warning restore CS0649, IDE0044 + + public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } + + private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) + { + builder.Autoconnect(this); + + _titleId = titleId; + _virtualFileSystem = virtualFileSystem; + _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json"); + _baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]"; + + try + { + _dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, _serializerContext.ListDownloadableContentContainer); + } + catch + { + _dlcContainerList = new List<DownloadableContentContainer>(); + } + + _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); + + CellRendererToggle enableToggle = new(); + enableToggle.Toggled += (sender, args) => + { + _dlcTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + bool newValue = !(bool)_dlcTreeView.Model.GetValue(treeIter, 0); + _dlcTreeView.Model.SetValue(treeIter, 0, newValue); + + if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) + { + do + { + _dlcTreeView.Model.SetValue(childIter, 0, newValue); + } + while (_dlcTreeView.Model.IterNext(ref childIter)); + } + }; + + _dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0); + _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1); + _dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); + + foreach (DownloadableContentContainer dlcContainer in _dlcContainerList) + { + if (File.Exists(dlcContainer.ContainerPath)) + { + // The parent tree item has its own "enabled" check box, but it's the actual + // nca entries that store the enabled / disabled state. A bit of a UI inconsistency. + // Maybe a tri-state check box would be better, but for now we check the parent + // "enabled" box if all child NCAs are enabled. Usually fine since each nsp has only one nca. + bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled); + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath); + + using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath); + + PartitionFileSystem pfs = new(); + pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + + _virtualFileSystem.ImportTickets(pfs); + + foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList) + { + using var ncaFile = new UniqueRef<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath); + + if (nca != null) + { + ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.FullPath); + } + } + } + else + { + // DLC file moved or renamed. Allow the user to remove it without crashing the whole dialog. + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}"); + } + } + } + + private Nca TryCreateNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(_virtualFileSystem.KeySet, ncaStorage); + } + catch (Exception exception) + { + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}"); + } + + return null; + } + + private void AddButton_Clicked(object sender, EventArgs args) + { + FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel") + { + SelectMultiple = true, + }; + + FileFilter filter = new() + { + Name = "Switch Game DLCs", + }; + filter.AddPattern("*.nsp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + foreach (string containerPath in fileChooser.Filenames) + { + if (!File.Exists(containerPath)) + { + return; + } + + using FileStream containerFile = File.OpenRead(containerPath); + + PartitionFileSystem pfs = new(); + pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + bool containsDlc = false; + + _virtualFileSystem.ImportTickets(pfs); + + TreeIter? parentIter = null; + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath); + + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId) + { + break; + } + + parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath); + + ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath); + containsDlc = true; + } + } + + if (!containsDlc) + { + GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!"); + } + } + } + + fileChooser.Dispose(); + } + + private void RemoveButton_Clicked(object sender, EventArgs args) + { + if (_dlcTreeSelection.GetSelected(out ITreeModel treeModel, out TreeIter treeIter)) + { + if (_dlcTreeView.Model.IterParent(out TreeIter parentIter, treeIter) && _dlcTreeView.Model.IterNChildren(parentIter) <= 1) + { + ((TreeStore)treeModel).Remove(ref parentIter); + } + else + { + ((TreeStore)treeModel).Remove(ref treeIter); + } + } + } + + private void RemoveAllButton_Clicked(object sender, EventArgs args) + { + List<TreeIter> toRemove = new(); + + if (_dlcTreeView.Model.GetIterFirst(out TreeIter iter)) + { + do + { + toRemove.Add(iter); + } + while (_dlcTreeView.Model.IterNext(ref iter)); + } + + foreach (TreeIter i in toRemove) + { + TreeIter j = i; + ((TreeStore)_dlcTreeView.Model).Remove(ref j); + } + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + _dlcContainerList.Clear(); + + if (_dlcTreeView.Model.GetIterFirst(out TreeIter parentIter)) + { + do + { + if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) + { + DownloadableContentContainer dlcContainer = new() + { + ContainerPath = (string)_dlcTreeView.Model.GetValue(parentIter, 2), + DownloadableContentNcaList = new List<DownloadableContentNca>(), + }; + + do + { + dlcContainer.DownloadableContentNcaList.Add(new DownloadableContentNca + { + Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), + TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), + FullPath = (string)_dlcTreeView.Model.GetValue(childIter, 2), + }); + } + while (_dlcTreeView.Model.IterNext(ref childIter)); + + _dlcContainerList.Add(dlcContainer); + } + } + while (_dlcTreeView.Model.IterNext(ref parentIter)); + } + + JsonHelper.SerializeToFile(_dlcJsonPath, _dlcContainerList, _serializerContext.ListDownloadableContentContainer); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade new file mode 100644 index 00000000..bdb0e647 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkWindow" id="_dlcWindow"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - DLC Manager</property> + <property name="modal">True</property> + <property name="window_position">center</property> + <property name="default_width">550</property> + <property name="default_height">350</property> + <child> + <object class="GtkBox" id="MainBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="DlcBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="_baseTitleInfoLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="label" translatable="yes">Available DLC</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkTreeView" id="_dlcTreeView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_clickable">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="_dlcTreeSelection"/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="_addUpdate"> + <property name="label" translatable="yes">Add</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Adds a DLC to this list</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="AddButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_removeUpdate"> + <property name="label" translatable="yes">Remove</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Removes the selected DLC</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_removeAllButton"> + <property name="label" translatable="yes">Remove All</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Removes all DLCs</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="_saveButton"> + <property name="label" translatable="yes">Save</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_cancelButton"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="titlebar"> + <placeholder/> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs new file mode 100644 index 00000000..dc467c0f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -0,0 +1,847 @@ +using Gtk; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.GraphicsDriver; +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.Helper; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class SettingsWindow : Window + { + private readonly MainWindow _parent; + private readonly ListStore _gameDirsBoxStore; + private readonly ListStore _audioBackendStore; + private readonly TimeZoneContentManager _timeZoneContentManager; + private readonly HashSet<string> _validTzRegions; + + private long _systemTimeOffset; + private float _previousVolumeLevel; + private bool _directoryChanged = false; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] CheckButton _traceLogToggle; + [GUI] CheckButton _errorLogToggle; + [GUI] CheckButton _warningLogToggle; + [GUI] CheckButton _infoLogToggle; + [GUI] CheckButton _stubLogToggle; + [GUI] CheckButton _debugLogToggle; + [GUI] CheckButton _fileLogToggle; + [GUI] CheckButton _guestLogToggle; + [GUI] CheckButton _fsAccessLogToggle; + [GUI] Adjustment _fsLogSpinAdjustment; + [GUI] ComboBoxText _graphicsDebugLevel; + [GUI] CheckButton _dockedModeToggle; + [GUI] CheckButton _discordToggle; + [GUI] CheckButton _checkUpdatesToggle; + [GUI] CheckButton _showConfirmExitToggle; + [GUI] RadioButton _hideCursorNever; + [GUI] RadioButton _hideCursorOnIdle; + [GUI] RadioButton _hideCursorAlways; + [GUI] CheckButton _vSyncToggle; + [GUI] CheckButton _shaderCacheToggle; + [GUI] CheckButton _textureRecompressionToggle; + [GUI] CheckButton _macroHLEToggle; + [GUI] CheckButton _ptcToggle; + [GUI] CheckButton _internetToggle; + [GUI] CheckButton _fsicToggle; + [GUI] RadioButton _mmSoftware; + [GUI] RadioButton _mmHost; + [GUI] RadioButton _mmHostUnsafe; + [GUI] CheckButton _expandRamToggle; + [GUI] CheckButton _ignoreToggle; + [GUI] CheckButton _directKeyboardAccess; + [GUI] CheckButton _directMouseAccess; + [GUI] ComboBoxText _systemLanguageSelect; + [GUI] ComboBoxText _systemRegionSelect; + [GUI] Entry _systemTimeZoneEntry; + [GUI] EntryCompletion _systemTimeZoneCompletion; + [GUI] Box _audioBackendBox; + [GUI] ComboBox _audioBackendSelect; + [GUI] Label _audioVolumeLabel; + [GUI] Scale _audioVolumeSlider; + [GUI] SpinButton _systemTimeYearSpin; + [GUI] SpinButton _systemTimeMonthSpin; + [GUI] SpinButton _systemTimeDaySpin; + [GUI] SpinButton _systemTimeHourSpin; + [GUI] SpinButton _systemTimeMinuteSpin; + [GUI] Adjustment _systemTimeYearSpinAdjustment; + [GUI] Adjustment _systemTimeMonthSpinAdjustment; + [GUI] Adjustment _systemTimeDaySpinAdjustment; + [GUI] Adjustment _systemTimeHourSpinAdjustment; + [GUI] Adjustment _systemTimeMinuteSpinAdjustment; + [GUI] ComboBoxText _multiLanSelect; + [GUI] ComboBoxText _multiModeSelect; + [GUI] CheckButton _custThemeToggle; + [GUI] Entry _custThemePath; + [GUI] ToggleButton _browseThemePath; + [GUI] Label _custThemePathLabel; + [GUI] TreeView _gameDirsBox; + [GUI] Entry _addGameDirBox; + [GUI] ComboBoxText _galThreading; + [GUI] Entry _graphicsShadersDumpPath; + [GUI] ComboBoxText _anisotropy; + [GUI] ComboBoxText _aspectRatio; + [GUI] ComboBoxText _antiAliasing; + [GUI] ComboBoxText _scalingFilter; + [GUI] ComboBoxText _graphicsBackend; + [GUI] ComboBoxText _preferredGpu; + [GUI] ComboBoxText _resScaleCombo; + [GUI] Entry _resScaleText; + [GUI] Adjustment _scalingFilterLevel; + [GUI] Scale _scalingFilterSlider; + [GUI] ToggleButton _configureController1; + [GUI] ToggleButton _configureController2; + [GUI] ToggleButton _configureController3; + [GUI] ToggleButton _configureController4; + [GUI] ToggleButton _configureController5; + [GUI] ToggleButton _configureController6; + [GUI] ToggleButton _configureController7; + [GUI] ToggleButton _configureController8; + [GUI] ToggleButton _configureControllerH; + +#pragma warning restore CS0649, IDE0044 + + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + + private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + _parent = parent; + + builder.Autoconnect(this); + + _timeZoneContentManager = new TimeZoneContentManager(); + _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, IntegrityCheckLevel.None); + + _validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly. + + // Bind Events. + _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1); + _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2); + _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3); + _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4); + _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5); + _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6); + _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7); + _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8); + _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld); + _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; + + _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; + _scalingFilter.Changed += (sender, args) => _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; + _galThreading.Changed += (sender, args) => + { + if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()) + { + GtkDialog.CreateInfoDialog("Warning - Backend Threading", "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's."); + } + }; + + // Setup Currents. + if (ConfigurationState.Instance.Logger.EnableTrace) + { + _traceLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFileLog) + { + _fileLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableError) + { + _errorLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableWarn) + { + _warningLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableInfo) + { + _infoLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableStub) + { + _stubLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableDebug) + { + _debugLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableGuest) + { + _guestLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFsAccessLog) + { + _fsAccessLogToggle.Click(); + } + + foreach (GraphicsDebugLevel level in Enum.GetValues<GraphicsDebugLevel>()) + { + _graphicsDebugLevel.Append(level.ToString(), level.ToString()); + } + + _graphicsDebugLevel.SetActiveId(ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value.ToString()); + + if (ConfigurationState.Instance.System.EnableDockedMode) + { + _dockedModeToggle.Click(); + } + + if (ConfigurationState.Instance.EnableDiscordIntegration) + { + _discordToggle.Click(); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart) + { + _checkUpdatesToggle.Click(); + } + + if (ConfigurationState.Instance.ShowConfirmExit) + { + _showConfirmExitToggle.Click(); + } + + switch (ConfigurationState.Instance.HideCursor.Value) + { + case HideCursorMode.Never: + _hideCursorNever.Click(); + break; + case HideCursorMode.OnIdle: + _hideCursorOnIdle.Click(); + break; + case HideCursorMode.Always: + _hideCursorAlways.Click(); + break; + } + + if (ConfigurationState.Instance.Graphics.EnableVsync) + { + _vSyncToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableShaderCache) + { + _shaderCacheToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableTextureRecompression) + { + _textureRecompressionToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableMacroHLE) + { + _macroHLEToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnablePtc) + { + _ptcToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableInternetAccess) + { + _internetToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) + { + _fsicToggle.Click(); + } + + switch (ConfigurationState.Instance.System.MemoryManagerMode.Value) + { + case MemoryManagerMode.SoftwarePageTable: + _mmSoftware.Click(); + break; + case MemoryManagerMode.HostMapped: + _mmHost.Click(); + break; + case MemoryManagerMode.HostMappedUnsafe: + _mmHostUnsafe.Click(); + break; + } + + if (ConfigurationState.Instance.System.ExpandRam) + { + _expandRamToggle.Click(); + } + + if (ConfigurationState.Instance.System.IgnoreMissingServices) + { + _ignoreToggle.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + _directKeyboardAccess.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableMouse) + { + _directMouseAccess.Click(); + } + + if (ConfigurationState.Instance.UI.EnableCustomTheme) + { + _custThemeToggle.Click(); + } + + // Custom EntryCompletion Columns. If added to glade, need to override more signals + ListStore tzList = new(typeof(string), typeof(string), typeof(string)); + _systemTimeZoneCompletion.Model = tzList; + + CellRendererText offsetCol = new(); + CellRendererText abbrevCol = new(); + + _systemTimeZoneCompletion.PackStart(offsetCol, false); + _systemTimeZoneCompletion.AddAttribute(offsetCol, "text", 0); + _systemTimeZoneCompletion.TextColumn = 1; // Regions Column + _systemTimeZoneCompletion.PackStart(abbrevCol, false); + _systemTimeZoneCompletion.AddAttribute(abbrevCol, "text", 2); + + int maxLocationLength = 0; + + foreach (var (offset, location, abbr) in _timeZoneContentManager.ParseTzOffsets()) + { + var hours = Math.DivRem(offset, 3600, out int seconds); + var minutes = Math.Abs(seconds) / 60; + + var abbr2 = (abbr.StartsWith('+') || abbr.StartsWith('-')) ? string.Empty : abbr; + + tzList.AppendValues($"UTC{hours:+0#;-0#;+00}:{minutes:D2} ", location, abbr2); + _validTzRegions.Add(location); + + maxLocationLength = Math.Max(maxLocationLength, location.Length); + } + + _systemTimeZoneEntry.WidthChars = Math.Max(20, maxLocationLength + 1); // Ensure minimum Entry width + _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); + + _systemTimeZoneCompletion.MatchFunc = TimeZoneMatchFunc; + + _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); + _systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString()); + _galThreading.SetActiveId(ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()); + _resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString()); + _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); + _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); + _graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString()); + _antiAliasing.SetActiveId(((int)ConfigurationState.Instance.Graphics.AntiAliasing.Value).ToString()); + _scalingFilter.SetActiveId(((int)ConfigurationState.Instance.Graphics.ScalingFilter.Value).ToString()); + + UpdatePreferredGpuComboBox(); + + _graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox(); + PopulateNetworkInterfaces(); + _multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + _multiModeSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.Mode.Value.ToString()); + + _custThemePath.Buffer.Text = ConfigurationState.Instance.UI.CustomThemePath; + _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); + _scalingFilterLevel.Value = ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value; + _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; + _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; + _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; + _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; + + _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); + _gameDirsBoxStore = new ListStore(typeof(string)); + _gameDirsBox.Model = _gameDirsBoxStore; + + foreach (string gameDir in ConfigurationState.Instance.UI.GameDirs.Value) + { + _gameDirsBoxStore.AppendValues(gameDir); + } + + if (_custThemeToggle.Active == false) + { + _custThemePath.Sensitive = false; + _custThemePathLabel.Sensitive = false; + _browseThemePath.Sensitive = false; + } + + // Setup system time spinners + UpdateSystemTimeSpinners(); + + _audioBackendStore = new ListStore(typeof(string), typeof(AudioBackend)); + + TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl); + TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo); + TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2); + TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy); + + _audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore); + _audioBackendSelect.EntryTextColumn = 0; + _audioBackendSelect.Entry.IsEditable = false; + + switch (ConfigurationState.Instance.System.AudioBackend.Value) + { + case AudioBackend.OpenAl: + _audioBackendSelect.SetActiveIter(openAlIter); + break; + case AudioBackend.SoundIo: + _audioBackendSelect.SetActiveIter(soundIoIter); + break; + case AudioBackend.SDL2: + _audioBackendSelect.SetActiveIter(sdl2Iter); + break; + case AudioBackend.Dummy: + _audioBackendSelect.SetActiveIter(dummyIter); + break; + default: + throw new InvalidOperationException($"{nameof(ConfigurationState.Instance.System.AudioBackend)} contains an invalid value: {ConfigurationState.Instance.System.AudioBackend.Value}"); + } + + _audioBackendBox.Add(_audioBackendSelect); + _audioBackendSelect.Show(); + + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume; + _audioVolumeLabel = new Label("Volume: "); + _audioVolumeSlider = new Scale(Orientation.Horizontal, 0, 100, 1); + _audioVolumeLabel.MarginStart = 10; + _audioVolumeSlider.ValuePos = PositionType.Right; + _audioVolumeSlider.WidthRequest = 200; + + _audioVolumeSlider.Value = _previousVolumeLevel * 100; + _audioVolumeSlider.ValueChanged += VolumeSlider_OnChange; + _audioBackendBox.Add(_audioVolumeLabel); + _audioBackendBox.Add(_audioVolumeSlider); + _audioVolumeLabel.Show(); + _audioVolumeSlider.Show(); + + bool openAlIsSupported = false; + bool soundIoIsSupported = false; + bool sdl2IsSupported = false; + + Task.Run(() => + { + openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported; + soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported; + sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported; + }); + + // This function runs whenever the dropdown is opened + _audioBackendSelect.SetCellDataFunc(_audioBackendSelect.Cells[0], (layout, cell, model, iter) => + { + cell.Sensitive = ((AudioBackend)_audioBackendStore.GetValue(iter, 1)) switch + { + AudioBackend.OpenAl => openAlIsSupported, + AudioBackend.SoundIo => soundIoIsSupported, + AudioBackend.SDL2 => sdl2IsSupported, + AudioBackend.Dummy => true, + _ => throw new InvalidOperationException($"{nameof(_audioBackendStore)} contains an invalid value for iteration {iter}: {_audioBackendStore.GetValue(iter, 1)}"), + }; + }); + + if (OperatingSystem.IsMacOS()) + { + var store = (_graphicsBackend.Model as ListStore); + store.GetIter(out TreeIter openglIter, new TreePath(new[] { 1 })); + store.Remove(ref openglIter); + + _graphicsBackend.Model = store; + } + } + + private void UpdatePreferredGpuComboBox() + { + _preferredGpu.RemoveAll(); + + if (Enum.Parse<GraphicsBackend>(_graphicsBackend.ActiveId) == GraphicsBackend.Vulkan) + { + var devices = Graphics.Vulkan.VulkanRenderer.GetPhysicalDevices(); + string preferredGpuIdFromConfig = ConfigurationState.Instance.Graphics.PreferredGpu.Value; + string preferredGpuId = preferredGpuIdFromConfig; + bool noGpuId = string.IsNullOrEmpty(preferredGpuIdFromConfig); + + foreach (var device in devices) + { + string dGpu = device.IsDiscrete ? " (dGPU)" : ""; + _preferredGpu.Append(device.Id, $"{device.Name}{dGpu}"); + + // If there's no GPU selected yet, we just pick the first GPU. + // If there's a discrete GPU available, we always prefer that over the previous selection, + // as it is likely to have better performance and more features. + // If the configuration file already has a GPU selection, we always prefer that instead. + if (noGpuId && (string.IsNullOrEmpty(preferredGpuId) || device.IsDiscrete)) + { + preferredGpuId = device.Id; + } + } + + if (!string.IsNullOrEmpty(preferredGpuId)) + { + _preferredGpu.SetActiveId(preferredGpuId); + } + } + } + + private void PopulateNetworkInterfaces() + { + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (NetworkInterface nif in interfaces) + { + string guid = nif.Id; + string name = nif.Name; + + _multiLanSelect.Append(guid, name); + } + } + + private void UpdateSystemTimeSpinners() + { + //Bind system time events + _systemTimeYearSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeMonthSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeDaySpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeHourSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeMinuteSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + + //Apply actual system time + SystemTimeOffset to system time spin buttons + DateTime systemTime = DateTime.Now.AddSeconds(_systemTimeOffset); + + _systemTimeYearSpinAdjustment.Value = systemTime.Year; + _systemTimeMonthSpinAdjustment.Value = systemTime.Month; + _systemTimeDaySpinAdjustment.Value = systemTime.Day; + _systemTimeHourSpinAdjustment.Value = systemTime.Hour; + _systemTimeMinuteSpinAdjustment.Value = systemTime.Minute; + + //Format spin buttons text to include leading zeros + _systemTimeYearSpin.Text = systemTime.Year.ToString("0000"); + _systemTimeMonthSpin.Text = systemTime.Month.ToString("00"); + _systemTimeDaySpin.Text = systemTime.Day.ToString("00"); + _systemTimeHourSpin.Text = systemTime.Hour.ToString("00"); + _systemTimeMinuteSpin.Text = systemTime.Minute.ToString("00"); + + //Bind system time events + _systemTimeYearSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeMonthSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeDaySpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeHourSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeMinuteSpin.ValueChanged += SystemTimeSpin_ValueChanged; + } + + private void SaveSettings() + { + if (_directoryChanged) + { + List<string> gameDirs = new(); + + _gameDirsBoxStore.GetIterFirst(out TreeIter treeIter); + + for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++) + { + gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0)); + + _gameDirsBoxStore.IterNext(ref treeIter); + } + + ConfigurationState.Instance.UI.GameDirs.Value = gameDirs; + + _directoryChanged = false; + } + + HideCursorMode hideCursor = HideCursorMode.Never; + + if (_hideCursorOnIdle.Active) + { + hideCursor = HideCursorMode.OnIdle; + } + + if (_hideCursorAlways.Active) + { + hideCursor = HideCursorMode.Always; + } + + if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f) + { + resScaleCustom = 1.0f; + } + + if (_validTzRegions.Contains(_systemTimeZoneEntry.Text)) + { + ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneEntry.Text; + } + + MemoryManagerMode memoryMode = MemoryManagerMode.SoftwarePageTable; + + if (_mmHost.Active) + { + memoryMode = MemoryManagerMode.HostMapped; + } + + if (_mmHostUnsafe.Active) + { + memoryMode = MemoryManagerMode.HostMappedUnsafe; + } + + BackendThreading backendThreading = Enum.Parse<BackendThreading>(_galThreading.ActiveId); + if (ConfigurationState.Instance.Graphics.BackendThreading != backendThreading) + { + DriverUtilities.ToggleOGLThreading(backendThreading == BackendThreading.Off); + } + + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; + ConfigurationState.Instance.Logger.EnableTrace.Value = _traceLogToggle.Active; + ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; + ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; + ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; + ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; + ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; + ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId); + ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; + ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; + ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active; + ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active; + ConfigurationState.Instance.HideCursor.Value = hideCursor; + ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; + ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active; + ConfigurationState.Instance.Graphics.EnableTextureRecompression.Value = _textureRecompressionToggle.Active; + ConfigurationState.Instance.Graphics.EnableMacroHLE.Value = _macroHLEToggle.Active; + ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; + ConfigurationState.Instance.System.EnableInternetAccess.Value = _internetToggle.Active; + ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; + ConfigurationState.Instance.System.MemoryManagerMode.Value = memoryMode; + ConfigurationState.Instance.System.ExpandRam.Value = _expandRamToggle.Active; + ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; + ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; + ConfigurationState.Instance.Hid.EnableMouse.Value = _directMouseAccess.Active; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = _custThemeToggle.Active; + ConfigurationState.Instance.System.Language.Value = Enum.Parse<Language>(_systemLanguageSelect.ActiveId); + ConfigurationState.Instance.System.Region.Value = Enum.Parse<Common.Configuration.System.Region>(_systemRegionSelect.ActiveId); + ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset; + ConfigurationState.Instance.UI.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; + ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; + ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); + ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse<AspectRatio>(_aspectRatio.ActiveId); + ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = Enum.Parse<GraphicsBackend>(_graphicsBackend.ActiveId); + ConfigurationState.Instance.Graphics.PreferredGpu.Value = _preferredGpu.ActiveId; + ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); + ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; + ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; + ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId); + ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId); + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; + + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; + + ConfigurationState.Instance.Multiplayer.Mode.Value = Enum.Parse<MultiplayerMode>(_multiModeSelect.ActiveId); + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; + + if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter)) + { + ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1); + } + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + _parent.UpdateInternetAccess(); + MainWindow.UpdateGraphicsConfig(); + ThemeHelper.ApplyTheme(); + } + + // + // Events + // + private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e) + { + if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text)) + { + _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); + } + } + + private bool TimeZoneMatchFunc(EntryCompletion compl, string key, TreeIter iter) + { + key = key.Trim().Replace(' ', '_'); + + return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region + ((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr + ((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset + } + + private void SystemTimeSpin_ValueChanged(object sender, EventArgs e) + { + int year = _systemTimeYearSpin.ValueAsInt; + int month = _systemTimeMonthSpin.ValueAsInt; + int day = _systemTimeDaySpin.ValueAsInt; + int hour = _systemTimeHourSpin.ValueAsInt; + int minute = _systemTimeMinuteSpin.ValueAsInt; + + if (!DateTime.TryParse(year + "-" + month + "-" + day + " " + hour + ":" + minute, out DateTime newTime)) + { + UpdateSystemTimeSpinners(); + + return; + } + + newTime = newTime.AddSeconds(DateTime.Now.Second).AddMilliseconds(DateTime.Now.Millisecond); + + long systemTimeOffset = (long)Math.Ceiling((newTime - DateTime.Now).TotalMinutes) * 60L; + + if (_systemTimeOffset != systemTimeOffset) + { + _systemTimeOffset = systemTimeOffset; + UpdateSystemTimeSpinners(); + } + } + + private void AddDir_Pressed(object sender, EventArgs args) + { + if (Directory.Exists(_addGameDirBox.Buffer.Text)) + { + _gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text); + _directoryChanged = true; + } + else + { + FileChooserNative fileChooser = new("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Add", "Cancel") + { + SelectMultiple = true, + }; + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _directoryChanged = false; + foreach (string directory in fileChooser.Filenames) + { + if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter)) + { + do + { + if (directory.Equals((string)_gameDirsBoxStore.GetValue(treeIter, 0))) + { + break; + } + } while (_gameDirsBoxStore.IterNext(ref treeIter)); + } + + if (!_directoryChanged) + { + _gameDirsBoxStore.AppendValues(directory); + } + } + + _directoryChanged = true; + } + + fileChooser.Dispose(); + } + + _addGameDirBox.Buffer.Text = ""; + + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + } + + private void RemoveDir_Pressed(object sender, EventArgs args) + { + TreeSelection selection = _gameDirsBox.Selection; + + if (selection.GetSelected(out TreeIter treeIter)) + { + _gameDirsBoxStore.Remove(ref treeIter); + + _directoryChanged = true; + } + + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + } + + private void CustThemeToggle_Activated(object sender, EventArgs args) + { + _custThemePath.Sensitive = _custThemeToggle.Active; + _custThemePathLabel.Sensitive = _custThemeToggle.Active; + _browseThemePath.Sensitive = _custThemeToggle.Active; + } + + private void BrowseThemeDir_Pressed(object sender, EventArgs args) + { + using (FileChooserNative fileChooser = new("Choose the theme to load", this, FileChooserAction.Open, "Select", "Cancel")) + { + FileFilter filter = new() + { + Name = "Theme Files", + }; + filter.AddPattern("*.css"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _custThemePath.Buffer.Text = fileChooser.Filename; + } + } + + _browseThemePath.SetStateFlags(StateFlags.Normal, true); + } + + private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + ControllerWindow controllerWindow = new(_parent, playerIndex); + + controllerWindow.SetSizeRequest((int)(controllerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(controllerWindow.DefaultHeight * Program.WindowScaleFactor)); + controllerWindow.Show(); + } + + private void VolumeSlider_OnChange(object sender, EventArgs args) + { + ConfigurationState.Instance.System.AudioVolume.Value = (float)(_audioVolumeSlider.Value / 100); + } + + private void SaveToggle_Activated(object sender, EventArgs args) + { + SaveSettings(); + Dispose(); + } + + private void ApplyToggle_Activated(object sender, EventArgs args) + { + SaveSettings(); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + ConfigurationState.Instance.System.AudioVolume.Value = _previousVolumeLevel; + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade new file mode 100644 index 00000000..f0dbd6b6 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade @@ -0,0 +1,3221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkAdjustment" id="_fsLogSpinAdjustment"> + <property name="upper">3</property> + <property name="step-increment">1</property> + <property name="page-increment">10</property> + </object> + <object class="GtkAdjustment" id="_scalingFilterLevel"> + <property name="upper">101</property> + <property name="step-increment">1</property> + <property name="page-increment">5</property> + <property name="page-size">1</property> + </object> + <object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment"> + <property name="lower">1</property> + <property name="upper">31</property> + <property name="step-increment">1</property> + <property name="page-increment">5</property> + </object> + <object class="GtkAdjustment" id="_systemTimeHourSpinAdjustment"> + <property name="upper">23</property> + <property name="step-increment">1</property> + <property name="page-increment">5</property> + </object> + <object class="GtkAdjustment" id="_systemTimeMinuteSpinAdjustment"> + <property name="upper">59</property> + <property name="step-increment">1</property> + <property name="page-increment">5</property> + </object> + <object class="GtkAdjustment" id="_systemTimeMonthSpinAdjustment"> + <property name="lower">1</property> + <property name="upper">12</property> + <property name="step-increment">1</property> + <property name="page-increment">5</property> + </object> + <object class="GtkAdjustment" id="_systemTimeYearSpinAdjustment"> + <property name="lower">2000</property> + <property name="upper">2060</property> + <property name="step-increment">1</property> + <property name="page-increment">10</property> + </object> + <object class="GtkEntryCompletion" id="_systemTimeZoneCompletion"> + <property name="minimum-key-length">0</property> + <property name="inline-completion">True</property> + <property name="inline-selection">True</property> + </object> + <object class="GtkWindow" id="_settingsWin"> + <property name="can-focus">False</property> + <property name="title" translatable="yes">Ryujinx - Settings</property> + <property name="modal">True</property> + <property name="window-position">center</property> + <property name="default-width">650</property> + <property name="default-height">650</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkNotebook"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <child> + <object class="GtkBox" id="TabGeneral"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">10</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CatGeneral"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">General</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="General"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_discordToggle"> + <property name="label" translatable="yes">Enable Discord Rich Presence</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Choose whether or not to display Ryujinx on your "currently playing" Discord activity</property> + <property name="halign">start</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_checkUpdatesToggle"> + <property name="label" translatable="yes">Check for Updates on Launch</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">start</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_showConfirmExitToggle"> + <property name="label" translatable="yes">Show "Confirm Exit" Dialog</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">start</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_hideCursorBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Hide Cursor:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_hideCursorNever"> + <property name="label" translatable="yes">Never</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="active">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_hideCursorOnIdle"> + <property name="label" translatable="yes">On Idle</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + <property name="group">_hideCursorNever</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_hideCursorAlways"> + <property name="label" translatable="yes">Always</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + <property name="group">_hideCursorNever</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatGameDir"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Game Directories</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="margin-bottom">10</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkTreeView" id="_gameDirsBox"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="headers-visible">False</property> + <property name="headers-clickable">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + </object> + </child> + <style> + <class name="GameDir"/> + </style> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkEntry" id="_addGameDirBox"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Enter a game directory to add to the list</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_addDir"> + <property name="label" translatable="yes">Add</property> + <property name="width-request">80</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes"> Add a game directory to the list</property> + <property name="margin-left">5</property> + <signal name="toggled" handler="AddDir_Pressed" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_removeDir"> + <property name="label" translatable="yes">Remove</property> + <property name="width-request">80</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Remove selected game directory</property> + <property name="margin-left">5</property> + <signal name="toggled" handler="RemoveDir_Pressed" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatThemes"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Themes</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_custThemeToggle"> + <property name="label" translatable="yes">Use Custom Theme</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Enable or disable custom themes in the GUI</property> + <property name="halign">start</property> + <property name="draw-indicator">True</property> + <signal name="toggled" handler="CustThemeToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel" id="_custThemePathLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Path to custom GUI theme</property> + <property name="label" translatable="yes">Custom Theme Path:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_custThemePath"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Path to custom GUI theme</property> + <property name="valign">center</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_browseThemePath"> + <property name="label" translatable="yes">Browse...</property> + <property name="width-request">80</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Browse for a custom GUI theme</property> + <property name="margin-left">5</property> + <signal name="toggled" handler="BrowseThemeDir_Pressed" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">10</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">6</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">General</property> + </object> + <packing> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TabInput"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">10</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkCheckButton" id="_dockedModeToggle"> + <property name="label" translatable="yes">Enable Docked Mode</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality. Configure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode. Leave ON if unsure.</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">10</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_directKeyboardAccess"> + <property name="label" translatable="yes">Direct Keyboard Access</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">10</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_directMouseAccess"> + <property name="label" translatable="yes">Direct Mouse Access</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">10</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <!-- n-columns=5 n-rows=5 --> + <object class="GtkGrid" id="ControllerGrid"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="column-spacing">20</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController1"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 3</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController3"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">4</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 2</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController2"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Handheld</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureControllerH"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">4</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController6"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">4</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController5"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 7</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController7"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 4</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController4"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + <property name="label" translatable="yes">Player 8</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="_configureController8"> + <property name="label" translatable="yes">Configure</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="margin-left">20</property> + <property name="margin-right">20</property> + <property name="margin-top">20</property> + <property name="margin-bottom">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">3</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">3</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">3</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">3</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">3</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">4</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="left-attach">4</property> + <property name="top-attach">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Input</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TabSystem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">10</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CatCore"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Core</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="RegionBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System Region</property> + <property name="halign">end</property> + <property name="label" translatable="yes">System Region:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_systemRegionSelect"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System Region</property> + <property name="margin-left">5</property> + <items> + <item id="Japan" translatable="yes">Japan</item> + <item id="USA" translatable="yes">USA</item> + <item id="Europe" translatable="yes">Europe</item> + <item id="Australia" translatable="yes">Australia</item> + <item id="China" translatable="yes">China</item> + <item id="Korea" translatable="yes">Korea</item> + <item id="Taiwan" translatable="yes">Taiwan</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="LanguageBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System Language</property> + <property name="halign">end</property> + <property name="label" translatable="yes">System Language:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_systemLanguageSelect"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System Language</property> + <items> + <item id="AmericanEnglish" translatable="yes">American English</item> + <item id="BritishEnglish" translatable="yes">British English</item> + <item id="CanadianFrench" translatable="yes">Canadian French</item> + <item id="Chinese" translatable="yes">Chinese</item> + <item id="Dutch" translatable="yes">Dutch</item> + <item id="French" translatable="yes">French</item> + <item id="German" translatable="yes">German</item> + <item id="Italian" translatable="yes">Italian</item> + <item id="Japanese" translatable="yes">Japanese</item> + <item id="Korean" translatable="yes">Korean</item> + <item id="LatinAmericanSpanish" translatable="yes">Latin American Spanish</item> + <item id="Portuguese" translatable="yes">Portuguese</item> + <item id="Russian" translatable="yes">Russian</item> + <item id="SimplifiedChinese" translatable="yes">Simplified Chinese</item> + <item id="Spanish" translatable="yes">Spanish</item> + <item id="Taiwanese" translatable="yes">Taiwanese</item> + <item id="TraditionalChinese" translatable="yes">Traditional Chinese</item> + <item id="BrazilianPortuguese" translatable="yes">Brazilian Portuguese</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TimeZoneBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System TimeZone</property> + <property name="halign">end</property> + <property name="label" translatable="yes">System TimeZone:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_systemTimeZoneEntry"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Change System TimeZone</property> + <property name="margin-left">5</property> + <property name="completion">_systemTimeZoneCompletion</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TimeBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change System Time</property> + <property name="halign">end</property> + <property name="label" translatable="yes">System Time:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_systemTimeYearSpin"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="text" translatable="yes">2000</property> + <property name="orientation">vertical</property> + <property name="adjustment">_systemTimeYearSpinAdjustment</property> + <property name="wrap">True</property> + <property name="value">2000</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label">-</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_systemTimeMonthSpin"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="text" translatable="yes">1</property> + <property name="orientation">vertical</property> + <property name="adjustment">_systemTimeMonthSpinAdjustment</property> + <property name="wrap">True</property> + <property name="value">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label">-</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_systemTimeDaySpin"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="text" translatable="yes">1</property> + <property name="orientation">vertical</property> + <property name="adjustment">_systemTimeDaySpinAdjustment</property> + <property name="wrap">True</property> + <property name="value">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_systemTimeHourSpin"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="text" translatable="yes">0</property> + <property name="orientation">vertical</property> + <property name="adjustment">_systemTimeHourSpinAdjustment</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label">:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">7</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="_systemTimeMinuteSpin"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="text" translatable="yes">0</property> + <property name="orientation">vertical</property> + <property name="adjustment">_systemTimeMinuteSpinAdjustment</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">8</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_vSyncToggle"> + <property name="label" translatable="yes">Enable VSync</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck. Can be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it. Leave ON if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_ptcToggle"> + <property name="label" translatable="yes">Enable PPTC (Profiled Persistent Translation Cache)</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Saves translated JIT functions so that they do not need to be translated every time the game loads. Reduces stuttering and significantly speeds up boot times after the first boot of a game. Leave ON if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_internetToggle"> + <property name="label" translatable="yes">Enable Guest Internet Access</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Allows the emulated application to connect to the Internet. Games with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well. Does NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet. Leave OFF if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">7</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_fsicToggle"> + <property name="label" translatable="yes">Enable FS Integrity Checks</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log. Has no impact on performance and is meant to help troubleshooting. Leave ON if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">8</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_audioBackendBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <placeholder/> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Changes the backend used to render audio. SDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound. Set to SDL2 if unsure.</property> + <property name="halign">end</property> + <property name="margin-right">5</property> + <property name="label" translatable="yes">Audio Backend: </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="_memoryManagerBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <placeholder/> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance. Set to HOST UNCHECKED if unsure.</property> + <property name="halign">end</property> + <property name="margin-right">5</property> + <property name="label" translatable="yes">Memory Manager Mode: </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_mmSoftware"> + <property name="label" translatable="yes">Software</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Use a software page table for address translation. Highest accuracy but slowest performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_mmHost"> + <property name="label" translatable="yes">Host (fast)</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Directly map memory in the host address space. Much faster JIT compilation and execution.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + <property name="group">_mmSoftware</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="_mmHostUnsafe"> + <property name="label" translatable="yes">Host Unchecked (fastest, unsafe)</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + <property name="group">_mmSoftware</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatHacks"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Hacks</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes"> (may cause instability)</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_expandRamToggle"> + <property name="label" translatable="yes">Use alternative memory layout (Developers)</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Utilizes an alternative MemoryMode layout to mimic a Switch development model. This is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance. Leave OFF if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_ignoreToggle"> + <property name="label" translatable="yes">Ignore Missing Services</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games. Leave OFF if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">System</property> + </object> + <packing> + <property name="position">2</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TabGraphics"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CatFeatures"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Features</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="FeaturesOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure.</property> + <property name="label" translatable="yes">Graphics Backend Multithreading:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_galThreading"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure.</property> + <property name="active-id">-1</property> + <items> + <item id="Auto" translatable="yes">Auto</item> + <item id="Off" translatable="yes">Off</item> + <item id="On" translatable="yes">On</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Graphics Backend to use</property> + <property name="label" translatable="yes">Graphics Backend:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_graphicsBackend"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Graphics Backend to use</property> + <property name="active-id">-1</property> + <items> + <item id="0" translatable="yes">Vulkan</item> + <item id="1" translatable="yes">OpenGL</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Preferred GPU (Vulkan only)</property> + <property name="label" translatable="yes">Preferred GPU:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_preferredGpu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Preferred GPU (Vulkan only)</property> + <property name="active-id">-1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatEnhancements"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Enhancements</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="EnhancementOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_shaderCacheToggle"> + <property name="label" translatable="yes">Enable Shader Cache</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Saves a disk shader cache which reduces stuttering in subsequent runs. Leave ON if unsure.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_textureRecompressionToggle"> + <property name="label" translatable="yes">Enable Texture Recompression</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Enables or disables Texture Recompression. Reduces VRAM usage at the cost of texture quality, and may also increase stuttering</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_macroHLEToggle"> + <property name="label" translatable="yes">Enable Macro HLE</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Enables or disables high-level emulation of Macro code. Improves performance but may cause graphical glitches in some games</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Resolution Scale applied to applicable render targets.</property> + <property name="label" translatable="yes">Resolution Scale:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_resScaleCombo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Resolution Scale applied to applicable render targets.</property> + <property name="active-id">1</property> + <items> + <item id="1" translatable="yes">Native (720p/1080p)</item> + <item id="2" translatable="yes">2x (1440p/2160p)</item> + <item id="3" translatable="yes">3x (2160p/3240p)</item> + <item id="4" translatable="yes">4x (2880p/4320p)</item> + <item id="-1" translatable="yes">Custom (not recommended)</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_resScaleText"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.</property> + <property name="valign">center</property> + <property name="caps-lock-warning">False</property> + <property name="placeholder-text">1.0</property> + <property name="input-purpose">number</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Applies a final effect to the game render</property> + <property name="label" translatable="yes">Post Processing Effect:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_antiAliasing"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Applies anti-aliasing to the game render</property> + <property name="active-id">1</property> + <items> + <item id="0" translatable="yes">None</item> + <item id="1" translatable="yes">FXAA</item> + <item id="2" translatable="yes">SMAA Low</item> + <item id="3" translatable="yes">SMAA Medium</item> + <item id="4" translatable="yes">SMAA High</item> + <item id="5" translatable="yes">SMAA Ultra</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="width-request">100</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property> + <property name="label" translatable="yes">Upscale: </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_scalingFilter"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property> + <property name="active-id">1</property> + <items> + <item id="0" translatable="yes">Bilinear</item> + <item id="1" translatable="yes">Nearest</item> + <item id="2" translatable="yes">FSR</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScale" id="_scalingFilterSlider"> + <property name="width-request">200</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="margin-start">5</property> + <property name="adjustment">_scalingFilterLevel</property> + <property name="round-digits">1</property> + <property name="value-pos">right</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Level of Anisotropic Filtering (set to Auto to use the value requested by the game)</property> + <property name="label" translatable="yes">Anisotropic Filtering:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_anisotropy"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Level of Anisotropic Filtering (set to Auto to use the value requested by the game)</property> + <property name="active-id">-1</property> + <items> + <item id="-1" translatable="yes">Auto</item> + <item id="2" translatable="yes">2x</item> + <item id="4" translatable="yes">4x</item> + <item id="8" translatable="yes">8x</item> + <item id="16" translatable="yes">16x</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Aspect Ratio applied to the renderer window.</property> + <property name="label" translatable="yes">Aspect Ratio:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_aspectRatio"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Aspect Ratio applied to the renderer window.</property> + <property name="active-id">1</property> + <items> + <item id="0" translatable="yes">4:3</item> + <item id="1" translatable="yes">16:9</item> + <item id="2" translatable="yes">16:10</item> + <item id="3" translatable="yes">21:9</item> + <item id="4" translatable="yes">32:9</item> + <item id="5" translatable="yes">Stretch to Fit Window</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">7</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatDev"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Developer Options</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="DevOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Graphics Shaders Dump Path</property> + <property name="label" translatable="yes">Graphics Shaders Dump Path:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="_graphicsShadersDumpPath"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Graphics Shaders Dump Path</property> + <property name="valign">center</property> + <property name="caps-lock-warning">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Graphics</property> + </object> + <packing> + <property name="position">3</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TabLogging"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">10</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CatLogging"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Logging</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="LogggingOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="_fileLogToggle"> + <property name="label" translatable="yes">Enable Logging to File</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Saves console logging to a log file on disk. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_stubLogToggle"> + <property name="label" translatable="yes">Enable Stub Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints stub log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_infoLogToggle"> + <property name="label" translatable="yes">Enable Info Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints info log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_warningLogToggle"> + <property name="label" translatable="yes">Enable Warning Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints warning log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_errorLogToggle"> + <property name="label" translatable="yes">Enable Error Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints error log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_guestLogToggle"> + <property name="label" translatable="yes">Enable Guest Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints guest log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">7</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_fsAccessLogToggle"> + <property name="label" translatable="yes">Enable Fs Access Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">8</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> + <property name="label" translatable="yes">Fs Global Access Log Mode:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> + <property name="text" translatable="yes">0</property> + <property name="adjustment">_fsLogSpinAdjustment</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">9</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatDevLogging"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Use with care</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Developer Options (WARNING: Will reduce performance)</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="DevLoggingOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">5</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Requires appropriate log levels enabled.</property> + <property name="label" translatable="yes">Graphics Backend Log Level</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">22</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_graphicsDebugLevel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Requires appropriate log levels enabled.</property> + <property name="margin-left">5</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">22</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_debugLogToggle"> + <property name="label" translatable="yes">Enable Debug Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints debug log messages in the console. Only use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">21</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="_traceLogToggle"> + <property name="label" translatable="yes">Enable Trace Logs</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="tooltip-text" translatable="yes">Prints trace log messages in the console. Does not affect performance.</property> + <property name="halign">start</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">22</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">22</property> + </packing> + </child> + </object> + <packing> + <property name="position">4</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Logging</property> + </object> + <packing> + <property name="position">4</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="TabMultiplayer"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">10</property> + <property name="margin-top">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="CatMultiplayer"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">Multiplayer</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="MultiplayerOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="ModeBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Mode:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_multiModeSelect"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property> + <property name="active-id">Disabled</property> + <items> + <item id="Disabled" translatable="yes">Disabled</item> + <item id="LdnMitm" translatable="yes">ldn_mitm</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="CatLAN"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">LAN Mode</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="LANOptions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="NetworkInterfaceBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Network Interface:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="_multiLanSelect"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property> + <property name="active-id">0</property> + <items> + <item id="0" translatable="yes">Default</item> + </items> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-bottom">5</property> + <property name="label" translatable="yes">To use LAN functionality in games, Enable Guest Internet Access must be checked in System.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">5</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Multiplayer</property> + </object> + <packing> + <property name="position">5</property> + <property name="tab-fill">False</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButtonBox" id="_buttonBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-right">5</property> + <property name="margin-top">3</property> + <property name="margin-bottom">3</property> + <property name="spacing">5</property> + <property name="layout-style">end</property> + <child> + <object class="GtkToggleButton" id="SaveToggle"> + <property name="label" translatable="yes">Save</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="toggled" handler="SaveToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="CloseToggle"> + <property name="label" translatable="yes">Close</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="toggled" handler="CloseToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Apply</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="clicked" handler="ApplyToggle_Activated" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs new file mode 100644 index 00000000..74b2330e --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs @@ -0,0 +1,206 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using GUI = Gtk.Builder.ObjectAttribute; +using SpanHelpers = LibHac.Common.SpanHelpers; + +namespace Ryujinx.UI.Windows +{ + public class TitleUpdateWindow : Window + { + private readonly MainWindow _parent; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly string _titleId; + private readonly string _updateJsonPath; + + private TitleUpdateMetadata _titleUpdateWindowData; + + private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary; + private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] Box _availableUpdatesBox; + [GUI] RadioButton _noUpdateRadioButton; +#pragma warning restore CS0649, IDE0044 + + public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } + + private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) + { + _parent = parent; + + builder.Autoconnect(this); + + _titleId = titleId; + _virtualFileSystem = virtualFileSystem; + _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json"); + _radioButtonToPathDictionary = new Dictionary<RadioButton, string>(); + + try + { + _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, _serializerContext.TitleUpdateMetadata); + } + catch + { + _titleUpdateWindowData = new TitleUpdateMetadata + { + Selected = "", + Paths = new List<string>(), + }; + } + + _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]"; + + foreach (string path in _titleUpdateWindowData.Paths) + { + AddUpdate(path); + } + + if (_titleUpdateWindowData.Selected == "") + { + _noUpdateRadioButton.Active = true; + } + else + { + foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected)) + { + update.Active = true; + } + } + } + + private void AddUpdate(string path) + { + if (File.Exists(path)) + { + using FileStream file = new(path, FileMode.Open, FileAccess.Read); + + PartitionFileSystem nsp = new(); + nsp.Initialize(file.AsStorage()).ThrowIfFailure(); + + try + { + (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0); + + if (controlNca != null && patchNca != null) + { + ApplicationControlProperty controlData = new(); + + using var nacpFile = new UniqueRef<IFile>(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}"); + radioButton.JoinGroup(_noUpdateRadioButton); + + _availableUpdatesBox.Add(radioButton); + _radioButtonToPathDictionary.Add(radioButton, path); + + radioButton.Show(); + radioButton.Active = true; + } + else + { + GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!"); + } + } + catch (Exception exception) + { + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}"); + } + } + } + + private void RemoveUpdates(bool removeSelectedOnly = false) + { + foreach (RadioButton radioButton in _noUpdateRadioButton.Group) + { + if (radioButton.Label != "No Update" && (!removeSelectedOnly || radioButton.Active)) + { + _availableUpdatesBox.Remove(radioButton); + _radioButtonToPathDictionary.Remove(radioButton); + radioButton.Dispose(); + } + } + } + + private void AddButton_Clicked(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Select update files", this, FileChooserAction.Open, "Add", "Cancel"); + + fileChooser.SelectMultiple = true; + + FileFilter filter = new() + { + Name = "Switch Game Updates", + }; + filter.AddPattern("*.nsp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + foreach (string path in fileChooser.Filenames) + { + AddUpdate(path); + } + } + } + + private void RemoveButton_Clicked(object sender, EventArgs args) + { + RemoveUpdates(true); + } + + private void RemoveAllButton_Clicked(object sender, EventArgs args) + { + RemoveUpdates(); + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + _titleUpdateWindowData.Paths.Clear(); + _titleUpdateWindowData.Selected = ""; + + foreach (string paths in _radioButtonToPathDictionary.Values) + { + _titleUpdateWindowData.Paths.Add(paths); + } + + foreach (RadioButton radioButton in _noUpdateRadioButton.Group) + { + if (radioButton.Active) + { + _titleUpdateWindowData.Selected = _radioButtonToPathDictionary.TryGetValue(radioButton, out string updatePath) ? updatePath : ""; + } + } + + JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _serializerContext.TitleUpdateMetadata); + + _parent.UpdateGameTable(); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade new file mode 100644 index 00000000..cfbac86d --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade @@ -0,0 +1,214 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkWindow" id="_titleUpdateWindow"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Ryujinx - Title Update Manager</property> + <property name="modal">True</property> + <property name="window_position">center</property> + <property name="default_width">550</property> + <property name="default_height">250</property> + <child> + <object class="GtkBox" id="MainBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="UpdatesBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="_baseTitleInfoLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="label" translatable="yes">Available Updates</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_left">10</property> + <property name="margin_right">10</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox" id="_availableUpdatesBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkRadioButton" id="_noUpdateRadioButton"> + <property name="label" translatable="yes">No Update</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="_addUpdate"> + <property name="label" translatable="yes">Add</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Adds an update to this list</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="AddButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_removeUpdate"> + <property name="label" translatable="yes">Remove</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Removes the selected update</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_removeAllButton"> + <property name="label" translatable="yes">Remove All</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Removes all the updates</property> + <property name="margin_left">10</property> + <signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="_saveButton"> + <property name="label" translatable="yes">Save</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="_cancelButton"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="margin_right">10</property> + <property name="margin_top">2</property> + <property name="margin_bottom">2</property> + <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="titlebar"> + <placeholder/> + </child> + </object> +</interface> diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs new file mode 100644 index 00000000..bc5a18f9 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs @@ -0,0 +1,255 @@ +using Gtk; +using Pango; +using System; + +namespace Ryujinx.UI.Windows +{ + public partial class UserProfilesManagerWindow : Window + { + private Box _mainBox; + private Label _selectedLabel; + private Box _selectedUserBox; + private Image _selectedUserImage; + private Box _selectedUserInfoBox; + private Entry _selectedUserNameEntry; + private Label _selectedUserIdLabel; + private Box _selectedUserButtonsBox; + private Button _saveProfileNameButton; + private Button _changeProfileImageButton; + private Box _usersTreeViewBox; + private Label _availableUsersLabel; + private ScrolledWindow _usersTreeViewWindow; + private ListStore _tableStore; + private TreeView _usersTreeView; + private Box _bottomBox; + private Button _addButton; + private Button _deleteButton; + private Button _closeButton; + + private void InitializeComponent() + { + // + // UserProfilesManagerWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 620; + DefaultHeight = 548; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Vertical, 0); + + // + // _selectedLabel + // + _selectedLabel = new Label("Selected User Profile:") + { + Margin = 15, + Attributes = new AttrList(), + Halign = Align.Start, + }; + _selectedLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + + // + // _viewBox + // + _usersTreeViewBox = new Box(Orientation.Vertical, 0); + + // + // _SelectedUserBox + // + _selectedUserBox = new Box(Orientation.Horizontal, 0) + { + MarginStart = 30, + }; + + // + // _selectedUserImage + // + _selectedUserImage = new Image(); + + // + // _selectedUserInfoBox + // + _selectedUserInfoBox = new Box(Orientation.Vertical, 0) + { + Homogeneous = true, + }; + + // + // _selectedUserNameEntry + // + _selectedUserNameEntry = new Entry("") + { + MarginStart = 15, + MaxLength = (int)MaxProfileNameLength, + }; + _selectedUserNameEntry.KeyReleaseEvent += SelectedUserNameEntry_KeyReleaseEvent; + + // + // _selectedUserIdLabel + // + _selectedUserIdLabel = new Label("") + { + MarginTop = 15, + MarginStart = 15, + }; + + // + // _selectedUserButtonsBox + // + _selectedUserButtonsBox = new Box(Orientation.Vertical, 0) + { + MarginEnd = 30, + }; + + // + // _saveProfileNameButton + // + _saveProfileNameButton = new Button() + { + Label = "Save Profile Name", + CanFocus = true, + ReceivesDefault = true, + Sensitive = false, + }; + _saveProfileNameButton.Clicked += EditProfileNameButton_Pressed; + + // + // _changeProfileImageButton + // + _changeProfileImageButton = new Button() + { + Label = "Change Profile Image", + CanFocus = true, + ReceivesDefault = true, + MarginTop = 10, + }; + _changeProfileImageButton.Clicked += ChangeProfileImageButton_Pressed; + + // + // _availableUsersLabel + // + _availableUsersLabel = new Label("Available User Profiles:") + { + Margin = 15, + Attributes = new AttrList(), + Halign = Align.Start, + }; + _availableUsersLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + + // + // _usersTreeViewWindow + // + _usersTreeViewWindow = new ScrolledWindow() + { + ShadowType = ShadowType.In, + CanFocus = true, + Expand = true, + MarginStart = 30, + MarginEnd = 30, + MarginBottom = 15, + }; + + // + // _tableStore + // + _tableStore = new ListStore(typeof(bool), typeof(Gdk.Pixbuf), typeof(string), typeof(Gdk.RGBA)); + + // + // _usersTreeView + // + _usersTreeView = new TreeView(_tableStore) + { + HoverSelection = true, + HeadersVisible = false, + }; + _usersTreeView.RowActivated += UsersTreeView_Activated; + + // + // _bottomBox + // + _bottomBox = new Box(Orientation.Horizontal, 0) + { + MarginStart = 30, + MarginEnd = 30, + MarginBottom = 15, + }; + + // + // _addButton + // + _addButton = new Button() + { + Label = "Add New Profile", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + }; + _addButton.Clicked += AddButton_Pressed; + + // + // _deleteButton + // + _deleteButton = new Button() + { + Label = "Delete Selected Profile", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + MarginStart = 10, + }; + _deleteButton.Clicked += DeleteButton_Pressed; + + // + // _closeButton + // + _closeButton = new Button() + { + Label = "Close", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + WidthRequest = 80, + }; + _closeButton.Clicked += CloseButton_Pressed; + + ShowComponent(); + } + + private void ShowComponent() + { + _usersTreeViewWindow.Add(_usersTreeView); + + _usersTreeViewBox.Add(_usersTreeViewWindow); + _bottomBox.PackStart(_addButton, false, false, 0); + _bottomBox.PackStart(_deleteButton, false, false, 0); + _bottomBox.PackEnd(_closeButton, false, false, 0); + + _selectedUserInfoBox.Add(_selectedUserNameEntry); + _selectedUserInfoBox.Add(_selectedUserIdLabel); + + _selectedUserButtonsBox.Add(_saveProfileNameButton); + _selectedUserButtonsBox.Add(_changeProfileImageButton); + + _selectedUserBox.Add(_selectedUserImage); + _selectedUserBox.PackStart(_selectedUserInfoBox, false, false, 0); + _selectedUserBox.PackEnd(_selectedUserButtonsBox, false, false, 0); + + _mainBox.PackStart(_selectedLabel, false, false, 0); + _mainBox.PackStart(_selectedUserBox, false, true, 0); + _mainBox.PackStart(_availableUsersLabel, false, false, 0); + _mainBox.Add(_usersTreeViewBox); + _mainBox.Add(_bottomBox); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs new file mode 100644 index 00000000..d1e5fa9f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs @@ -0,0 +1,328 @@ +using Gtk; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Image = SixLabors.ImageSharp.Image; + +namespace Ryujinx.UI.Windows +{ + public partial class UserProfilesManagerWindow : Window + { + private const uint MaxProfileNameLength = 0x20; + + private readonly AccountManager _accountManager; + private readonly ContentManager _contentManager; + + private byte[] _bufferImageProfile; + private string _tempNewProfileName; + + private Gdk.RGBA _selectedColor; + + private readonly ManualResetEvent _avatarsPreloadingEvent = new(false); + + public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + InitializeComponent(); + + _selectedColor.Red = 0.212; + _selectedColor.Green = 0.843; + _selectedColor.Blue = 0.718; + _selectedColor.Alpha = 1; + + _accountManager = accountManager; + _contentManager = contentManager; + + CellRendererToggle userSelectedToggle = new(); + userSelectedToggle.Toggled += UserSelectedToggle_Toggled; + + // NOTE: Uncomment following line when multiple selection of user profiles is supported. + //_usersTreeView.AppendColumn("Selected", userSelectedToggle, "active", 0); + _usersTreeView.AppendColumn("User Icon", new CellRendererPixbuf(), "pixbuf", 1); + _usersTreeView.AppendColumn("User Info", new CellRendererText(), "text", 2, "background-rgba", 3); + + _tableStore.SetSortColumnId(0, SortType.Descending); + + RefreshList(); + + if (_contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + AvatarWindow.PreloadAvatars(contentManager, virtualFileSystem); + _avatarsPreloadingEvent.Set(); + }); + } + } + + public void RefreshList() + { + _tableStore.Clear(); + + foreach (UserProfile userProfile in _accountManager.GetAllUsers()) + { + _tableStore.AppendValues(userProfile.AccountState == AccountState.Open, new Gdk.Pixbuf(userProfile.Image, 96, 96), $"{userProfile.Name}\n{userProfile.UserId}", Gdk.RGBA.Zero); + + if (userProfile.AccountState == AccountState.Open) + { + _selectedUserImage.Pixbuf = new Gdk.Pixbuf(userProfile.Image, 96, 96); + _selectedUserIdLabel.Text = userProfile.UserId.ToString(); + _selectedUserNameEntry.Text = userProfile.Name; + + _deleteButton.Sensitive = userProfile.UserId != AccountManager.DefaultUserId; + + _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); + _tableStore.SetValue(firstIter, 3, _selectedColor); + } + } + } + + // + // Events + // + + private void UsersTreeView_Activated(object o, RowActivatedArgs args) + { + SelectUserTreeView(); + } + + private void UserSelectedToggle_Toggled(object o, ToggledArgs args) + { + SelectUserTreeView(); + } + + private void SelectUserTreeView() + { + // Get selected item informations. + _usersTreeView.Selection.GetSelected(out TreeIter selectedIter); + + Gdk.Pixbuf userPicture = (Gdk.Pixbuf)_tableStore.GetValue(selectedIter, 1); + + string userName = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[0]; + string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1]; + + // Unselect the first user. + _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); + _tableStore.SetValue(firstIter, 0, false); + _tableStore.SetValue(firstIter, 3, Gdk.RGBA.Zero); + + // Set new informations. + _tableStore.SetValue(selectedIter, 0, true); + + _selectedUserImage.Pixbuf = userPicture; + _selectedUserNameEntry.Text = userName; + _selectedUserIdLabel.Text = userId; + _saveProfileNameButton.Sensitive = false; + + // Open the selected one. + _accountManager.OpenUser(new UserId(userId)); + + _deleteButton.Sensitive = userId != AccountManager.DefaultUserId.ToString(); + + _tableStore.SetValue(selectedIter, 3, _selectedColor); + } + + private void SelectedUserNameEntry_KeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + if (_saveProfileNameButton.Sensitive == false) + { + _saveProfileNameButton.Sensitive = true; + } + } + + private void AddButton_Pressed(object sender, EventArgs e) + { + _tempNewProfileName = GtkDialog.CreateInputDialog(this, "Choose the Profile Name", "Please Enter a Profile Name", MaxProfileNameLength); + + if (_tempNewProfileName != "") + { + SelectProfileImage(true); + + if (_bufferImageProfile != null) + { + AddUser(); + } + } + } + + private void DeleteButton_Pressed(object sender, EventArgs e) + { + if (GtkDialog.CreateChoiceDialog("Delete User Profile", "Are you sure you want to delete the profile ?", "Deleting this profile will also delete all associated save data.")) + { + _accountManager.DeleteUser(GetSelectedUserId()); + + RefreshList(); + } + } + + private void EditProfileNameButton_Pressed(object sender, EventArgs e) + { + _saveProfileNameButton.Sensitive = false; + + _accountManager.SetUserName(GetSelectedUserId(), _selectedUserNameEntry.Text); + + RefreshList(); + } + + private void ProcessProfileImage(byte[] buffer) + { + using Image image = Image.Load(buffer); + + image.Mutate(x => x.Resize(256, 256)); + + using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); + + image.SaveAsJpeg(streamJpg); + + _bufferImageProfile = streamJpg.ToArray(); + } + + private void ProfileImageFileChooser() + { + FileChooserNative fileChooser = new("Import Custom Profile Image", this, FileChooserAction.Open, "Import", "Cancel") + { + SelectMultiple = false, + }; + + FileFilter filter = new() + { + Name = "Custom Profile Images", + }; + filter.AddPattern("*.jpg"); + filter.AddPattern("*.jpeg"); + filter.AddPattern("*.png"); + filter.AddPattern("*.bmp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + ProcessProfileImage(File.ReadAllBytes(fileChooser.Filename)); + } + + fileChooser.Dispose(); + } + + private void SelectProfileImage(bool newUser = false) + { + if (_contentManager.GetCurrentFirmwareVersion() == null) + { + ProfileImageFileChooser(); + } + else + { + Dictionary<int, string> buttons = new() + { + { 0, "Import Image File" }, + { 1, "Select Firmware Avatar" }, + }; + + ResponseType responseDialog = GtkDialog.CreateCustomDialog("Profile Image Selection", + "Choose a Profile Image", + "You may import a custom profile image, or select an avatar from the system firmware.", + buttons, MessageType.Question); + + if (responseDialog == 0) + { + ProfileImageFileChooser(); + } + else if (responseDialog == (ResponseType)1) + { + AvatarWindow avatarWindow = new() + { + NewUser = newUser, + }; + + avatarWindow.DeleteEvent += AvatarWindow_DeleteEvent; + + avatarWindow.SetSizeRequest((int)(avatarWindow.DefaultWidth * Program.WindowScaleFactor), (int)(avatarWindow.DefaultHeight * Program.WindowScaleFactor)); + avatarWindow.Show(); + } + } + } + + private void ChangeProfileImageButton_Pressed(object sender, EventArgs e) + { + if (_contentManager.GetCurrentFirmwareVersion() != null) + { + _avatarsPreloadingEvent.WaitOne(); + } + + SelectProfileImage(); + + if (_bufferImageProfile != null) + { + SetUserImage(); + } + } + + private void AvatarWindow_DeleteEvent(object sender, DeleteEventArgs args) + { + _bufferImageProfile = ((AvatarWindow)sender).SelectedProfileImage; + + if (_bufferImageProfile != null) + { + if (((AvatarWindow)sender).NewUser) + { + AddUser(); + } + else + { + SetUserImage(); + } + } + } + + private void AddUser() + { + _accountManager.AddUser(_tempNewProfileName, _bufferImageProfile); + + _bufferImageProfile = null; + _tempNewProfileName = ""; + + RefreshList(); + } + + private void SetUserImage() + { + _accountManager.SetUserImage(GetSelectedUserId(), _bufferImageProfile); + + _bufferImageProfile = null; + + RefreshList(); + } + + private UserId GetSelectedUserId() + { + if (_usersTreeView.Model.GetIterFirst(out TreeIter iter)) + { + do + { + if ((bool)_tableStore.GetValue(iter, 0)) + { + break; + } + } + while (_usersTreeView.Model.IterNext(ref iter)); + } + + return new UserId(_tableStore.GetValue(iter, 2).ToString().Split("\n")[1]); + } + + private void CloseButton_Pressed(object sender, EventArgs e) + { + Close(); + } + } +} diff --git a/src/Ryujinx/App.axaml b/src/Ryujinx/App.axaml new file mode 100644 index 00000000..eab318b7 --- /dev/null +++ b/src/Ryujinx/App.axaml @@ -0,0 +1,17 @@ +<Application + x:Class="Ryujinx.Ava.App" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:sty="using:FluentAvalonia.Styling"> + <Application.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <MergeResourceInclude Source="/Assets/Styles/Themes.xaml"/> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Application.Resources> + <Application.Styles> + <sty:FluentAvaloniaTheme PreferSystemTheme="False" /> + <StyleInclude Source="/Assets/Styles/Styles.xaml"/> + </Application.Styles> +</Application> \ No newline at end of file diff --git a/src/Ryujinx/App.axaml.cs b/src/Ryujinx/App.axaml.cs new file mode 100644 index 00000000..387a6dc1 --- /dev/null +++ b/src/Ryujinx/App.axaml.cs @@ -0,0 +1,115 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +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 System; +using System.Diagnostics; + +namespace Ryujinx.Ava +{ + public class App : Application + { + public override void Initialize() + { + Name = $"Ryujinx {Program.Version}"; + + AvaloniaXamlLoader.Load(this); + + if (OperatingSystem.IsMacOS()) + { + Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false"); + } + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + + if (Program.PreviewerDetached) + { + ApplyConfiguredTheme(); + + ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event; + } + } + + private void CustomThemeChanged_Event(object sender, ReactiveEventArgs<bool> e) + { + ApplyConfiguredTheme(); + } + + private void ShowRestartDialog() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Dispatcher.UIThread.InvokeAsync(async () => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage], + LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]); + + if (result == UserResult.Yes) + { + var path = Environment.ProcessPath; + var proc = Process.Start(path, CommandLineState.Arguments); + desktop.Shutdown(); + Environment.Exit(0); + } + } + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e) + { + ApplyConfiguredTheme(); + } + + private void ApplyConfiguredTheme() + { + try + { + string baseStyle = ConfigurationState.Instance.UI.BaseStyle; + + if (string.IsNullOrWhiteSpace(baseStyle)) + { + ConfigurationState.Instance.UI.BaseStyle.Value = "Dark"; + + baseStyle = ConfigurationState.Instance.UI.BaseStyle; + } + + RequestedThemeVariant = baseStyle switch + { + "Light" => ThemeVariant.Light, + "Dark" => ThemeVariant.Dark, + _ => ThemeVariant.Default, + }; + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); + + ShowRestartDialog(); + } + } + } +} diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs new file mode 100644 index 00000000..04cec957 --- /dev/null +++ b/src/Ryujinx/AppHost.cs @@ -0,0 +1,1195 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +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 Silk.NET.Vulkan; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SPB.Graphics.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; +using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; +using Image = SixLabors.ImageSharp.Image; +using InputManager = Ryujinx.Input.HLE.InputManager; +using IRenderer = Ryujinx.Graphics.GAL.IRenderer; +using Key = Ryujinx.Input.Key; +using MouseButton = Ryujinx.Input.MouseButton; +using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; +using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Ava +{ + internal class AppHost + { + private const int CursorHideIdleTime = 5; // Hide Cursor seconds. + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const int TargetFps = 60; + private const float VolumeDelta = 0.05f; + + private static readonly Cursor _invisibleCursor = new(StandardCursorType.None); + private readonly IntPtr _invisibleCursorWin; + private readonly IntPtr _defaultCursorWin; + + private readonly long _ticksPerFrame; + private readonly Stopwatch _chrono; + private long _ticks; + + private readonly AccountManager _accountManager; + private readonly UserChannelPersistence _userChannelPersistence; + private readonly InputManager _inputManager; + + private readonly MainWindowViewModel _viewModel; + private readonly IKeyboard _keyboardInterface; + private readonly TopLevel _topLevel; + public RendererHost RendererHost; + + private readonly GraphicsDebugLevel _glLogLevel; + private float _newVolume; + private KeyboardHotkeyState _prevHotkeyState; + + private long _lastCursorMoveTime; + private bool _isCursorInRenderer = true; + + private bool _isStopped; + private bool _isActive; + private bool _renderingStarted; + + private readonly ManualResetEvent _gpuDoneEvent; + + private IRenderer _renderer; + private readonly Thread _renderingThread; + private readonly CancellationTokenSource _gpuCancellationTokenSource; + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private bool _dialogShown; + private readonly bool _isFirmwareTitle; + + private readonly object _lockObject = new(); + + public event EventHandler AppExit; + public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; + + public VirtualFileSystem VirtualFileSystem { get; } + public ContentManager ContentManager { get; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; set; } + + public int Width { get; private set; } + public int Height { get; private set; } + public string ApplicationPath { get; private set; } + public bool ScreenshotRequested { get; set; } + + public AppHost( + RendererHost renderer, + InputManager inputManager, + string applicationPath, + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + MainWindowViewModel viewmodel, + TopLevel topLevel) + { + _viewModel = viewmodel; + _inputManager = inputManager; + _accountManager = accountManager; + _userChannelPersistence = userChannelPersistence; + _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" }; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; + _topLevel = topLevel; + + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer)); + + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + ApplicationPath = applicationPath; + VirtualFileSystem = virtualFileSystem; + ContentManager = contentManager; + + RendererHost = renderer; + + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + if (ApplicationPath.StartsWith("@SystemContent")) + { + ApplicationPath = VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); + + _isFirmwareTitle = true; + } + + ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed; + + _topLevel.PointerMoved += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited += TopLevel_PointerExited; + + if (OperatingSystem.IsWindows()) + { + _invisibleCursorWin = CreateEmptyCursor(); + _defaultCursorWin = CreateArrowCursor(); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; + + ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; + + _gpuCancellationTokenSource = new CancellationTokenSource(); + _gpuDoneEvent = new ManualResetEvent(false); + } + + private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) + { + if (sender is MainWindow window) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + + _isCursorInRenderer = point.X >= bounds.X && + point.X <= bounds.Width + bounds.X && + point.Y >= bounds.Y && + point.Y <= bounds.Height + bounds.Y; + } + } + + private void TopLevel_PointerExited(object sender, PointerEventArgs e) + { + _isCursorInRenderer = false; + } + + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs<ScalingFilter> e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs<bool> e) + { + _renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + } + + private void ShowCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = Cursor.Default; + + if (OperatingSystem.IsWindows()) + { + SetCursor(_defaultCursorWin); + } + }); + } + + private void HideCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = _invisibleCursor; + + if (OperatingSystem.IsWindows()) + { + SetCursor(_invisibleCursorWin); + } + }); + } + + private void SetRendererWindowSize(Size size) + { + if (_renderer != null) + { + double scale = _topLevel.RenderScaling; + + _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + } + } + + private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (_lockObject) + { + DateTime currentTime = DateTime.Now; + string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), + }; + + string path = Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) + : Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); + + if (e.FlipX) + { + image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + if (e.FlipY) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + + image.SaveAsPng(path, new PngEncoder + { + ColorType = PngColorType.Rgb, + }); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Start() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(Device); + + _viewModel.IsGameRunning = true; + + Dispatcher.UIThread.InvokeAsync(() => + { + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version); + }); + + _viewModel.SetUiProgressHandlers(Device); + + RendererHost.BoundsChanged += Window_BoundsChanged; + + _isActive = true; + + _renderingThread.Start(); + + _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; + + MainLoop(); + + Exit(); + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args) + { + if (Device != null) + { + Device.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args) + { + if (Device != null) + { + Device.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e) + { + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e) + { + Device?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e) + { + Device?.SetVolume(e.NewValue); + + Dispatcher.UIThread.Post(() => + { + _viewModel.Volume = e.NewValue; + }); + } + + private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e) + { + Device.Configuration.EnableInternetAccess = e.NewValue; + } + + private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e) + { + Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; + } + + private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs<MultiplayerMode> e) + { + Device.Configuration.MultiplayerMode = e.NewValue; + } + + public void Stop() + { + _isActive = false; + } + + private void Exit() + { + (_keyboardInterface as AvaloniaKeyboard)?.Clear(); + + if (_isStopped) + { + return; + } + + _isStopped = true; + _isActive = false; + } + + public void DisposeContext() + { + Dispose(); + + _isActive = false; + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + + DisplaySleep.Restore(); + + NpadManager.Dispose(); + TouchScreenManager.Dispose(); + Device.Dispose(); + + DisposeGpu(); + + AppExit?.Invoke(this, EventArgs.Empty); + } + + private void Dispose() + { + if (Device.Processes != null) + { + MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough; + + _topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited -= TopLevel_PointerExited; + + _gpuCancellationTokenSource.Cancel(); + _gpuCancellationTokenSource.Dispose(); + + _chrono.Stop(); + } + + public void DisposeGpu() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + if (RendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow) + { + // Try to bind the OpenGL context before calling the shutdown event. + openGlWindow.MakeCurrent(false, false); + + Device.DisposeGpu(); + + // Unbind context and destroy everything. + openGlWindow.MakeCurrent(true, false); + } + else + { + Device.DisposeGpu(); + } + } + + private void HideCursorState_Changed(object sender, ReactiveEventArgs<HideCursorMode> state) + { + if (state.NewValue == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + } + + public async Task<bool> LoadGuestApplication() + { + InitializeSwitchInstance(); + MainWindow.UpdateGraphicsConfig(); + + SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) + { + { + if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) + { + if (userError == UserError.NoFirmware) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result != UserResult.Yes) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + + if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + + // Tell the user that we installed a firmware for them. + if (userError == UserError.NoFirmware) + { + firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + _viewModel.RefreshFirmwareStatus(); + + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } + else + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (_isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + else if (Directory.Exists(ApplicationPath)) + { + string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) + { + Device.Dispose(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!Device.LoadCart(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + } + else if (File.Exists(ApplicationPath)) + { + switch (Path.GetExtension(ApplicationPath).ToLowerInvariant()) + { + case ".xci": + { + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!Device.LoadXci(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nca": + { + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nsp": + case ".pfs0": + { + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!Device.LoadNsp(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + default: + { + Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); + + try + { + if (!Device.LoadProgram(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + Device.Dispose(); + + return false; + } + + break; + } + } + } + else + { + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + Device.Dispose(); + + return false; + } + + DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); + + ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.UpdatePreGame(); + }); + + return true; + } + + internal void Resume() + { + Device?.System.TogglePauseEmulation(false); + + _viewModel.IsPaused = false; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + } + + internal void Pause() + { + Device?.System.TogglePauseEmulation(true); + + _viewModel.IsPaused = true; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + } + + private void InitializeSwitchInstance() + { + // Initialize KeySet. + VirtualFileSystem.ReloadKeySet(); + + // Initialize Renderer. + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + renderer = new VulkanRenderer( + Vk.GetApi(), + (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, + VulkanHelper.GetRequiredInstanceExtensions, + ConfigurationState.Instance.Graphics.PreferredGpu.Value); + } + else + { + renderer = new OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + var isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + if (isGALThreaded) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}"); + + // Initialize Configuration. + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; + + HLEConfiguration configuration = new(VirtualFileSystem, + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); + + Device = new Switch(configuration); + } + + private static IHardwareDeviceDriver InitializeAudio() + { + var availableBackends = new List<AudioBackend> + { + AudioBackend.SDL2, + AudioBackend.SoundIo, + AudioBackend.OpenAl, + AudioBackend.Dummy, + }; + + AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; + + for (int i = 0; i < availableBackends.Count; i++) + { + if (availableBackends[i] == preferredBackend) + { + availableBackends.RemoveAt(i); + availableBackends.Insert(0, preferredBackend); + break; + } + } + + static IHardwareDeviceDriver InitializeAudioBackend<T>(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new() + { + if (T.IsSupported) + { + return new T(); + } + + Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}."); + + return null; + } + + IHardwareDeviceDriver deviceDriver = null; + + for (int i = 0; i < availableBackends.Count; i++) + { + AudioBackend currentBackend = availableBackends[i]; + AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy; + + deviceDriver = currentBackend switch + { + AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend), + AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend), + AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend), + _ => new DummyHardwareDeviceDriver(), + }; + + if (deviceDriver != null) + { + ConfigurationState.Instance.System.AudioBackend.Value = currentBackend; + break; + } + } + + MainWindowViewModel.SaveConfig(); + + return deviceDriver; + } + + private void Window_BoundsChanged(object sender, Size e) + { + Width = (int)e.Width; + Height = (int)e.Height; + + SetRendererWindowSize(e); + } + + private void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept. + Thread.Sleep(1); + } + } + + private void RenderLoop() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_viewModel.StartGamesInFullscreen) + { + _viewModel.WindowState = WindowState.FullScreen; + } + + if (_viewModel.WindowState == WindowState.FullScreen) + { + _viewModel.ShowMenuAndStatusBar = false; + } + }); + + _renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer; + + _renderer.ScreenCaptured += Renderer_ScreenCaptured; + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + _renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + _renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + + Width = (int)RendererHost.Bounds.Width; + Height = (int)RendererHost.Bounds.Height; + + _renderer.Window.SetSize((int)(Width * _topLevel.RenderScaling), (int)(Height * _topLevel.RenderScaling)); + + _chrono.Start(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + while (_isActive) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + if (!_renderingStarted) + { + _renderingStarted = true; + _viewModel.SwitchToRenderer(false); + } + + Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); + } + + if (_ticks >= _ticksPerFrame) + { + UpdateStatus(); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); + } + + public void UpdateStatus() + { + // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; + + if (GraphicsConfig.ResScale != 1) + { + dockedMode += $" ({GraphicsConfig.ResScale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", + ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", + $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + } + + public async Task ShowExitPrompt() + { + bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; + if (!shouldExit) + { + if (_dialogShown) + { + return; + } + + _dialogShown = true; + + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); + + _dialogShown = false; + } + + if (shouldExit) + { + Stop(); + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return false; + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if (_viewModel.IsActive) + { + if (_isCursorInRenderer) + { + if (ConfigurationState.Instance.Hid.EnableMouse) + { + HideCursor(); + } + else + { + switch (ConfigurationState.Instance.HideCursor.Value) + { + case HideCursorMode.Never: + ShowCursor(); + break; + case HideCursorMode.OnIdle: + if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) + { + HideCursor(); + } + else + { + ShowCursor(); + } + break; + case HideCursorMode.Always: + HideCursor(); + break; + } + } + } + else + { + ShowCursor(); + } + + Dispatcher.UIThread.Post(() => + { + if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + }); + + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState != _prevHotkeyState) + { + switch (currentHotkeyState) + { + case KeyboardHotkeyState.ToggleVSync: + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + + break; + case KeyboardHotkeyState.Screenshot: + ScreenshotRequested = true; + break; + case KeyboardHotkeyState.ShowUI: + _viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar; + break; + case KeyboardHotkeyState.Pause: + if (_viewModel.IsPaused) + { + Resume(); + } + else + { + Pause(); + } + break; + case KeyboardHotkeyState.ToggleMute: + if (Device.IsAudioMuted()) + { + Device.SetVolume(_viewModel.VolumeBeforeMute); + } + else + { + _viewModel.VolumeBeforeMute = Device.GetVolume(); + Device.SetVolume(0); + } + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.ResScaleUp: + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.ResScaleDown: + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.VolumeUp: + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.VolumeDown: + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.None: + (_keyboardInterface as AvaloniaKeyboard).Clear(); + break; + } + } + + _prevHotkeyState = currentHotkeyState; + + if (ScreenshotRequested) + { + ScreenshotRequested = false; + _renderer.Screenshot(); + } + } + + // Touchscreen. + bool hasTouch = false; + + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + Device.Hid.Touchscreen.Update(); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state = KeyboardHotkeyState.ToggleVSync; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state = KeyboardHotkeyState.Screenshot; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) + { + state = KeyboardHotkeyState.ShowUI; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state = KeyboardHotkeyState.Pause; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state = KeyboardHotkeyState.ToggleMute; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state = KeyboardHotkeyState.ResScaleUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state = KeyboardHotkeyState.ResScaleDown; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state = KeyboardHotkeyState.VolumeUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state = KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf new file mode 100644 index 00000000..8f05a4bb Binary files /dev/null and b/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf differ diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg new file mode 100644 index 00000000..cf78cf12 --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 1000 355.6" style="enable-background:new 0 0 1000 355.6;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#00BBDB;stroke:#000000;} + .st1{fill:#333333;stroke:#000000;stroke-width:0.93;} + .st2{fill:#333333;stroke:#000000;stroke-width:0.96;} + .st3{fill:#333333;stroke:#000000;stroke-width:0.85;} + .st4{fill:#1A1A1A;stroke:#000000;} + .st5{fill:#333333;stroke:#000000;} + .st6{fill:#1A1A1A;stroke:#1A1A1A;} + .st7{fill:#1A1A1A;stroke:#4D4D4D;} + .st8{stroke:#4D4D4D;} + .st9{stroke:#000000;} +</style> +<g id="g344"> + <path id="path66-7" d="M906.6,6.5v7.9c0,3.6-2.9,6.4-6.5,6.5H71.2c-3.6,0-6.4-2.9-6.5-6.5V6.5c0-3.6,2.9-6.4,6.5-6.5L207,0 + c4.9,0,9.6,1.2,14,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,13.9-3.4l269.7,0c4.9,0,9.6,1.2,14,3.4l13,6.7H716l13-6.7 + c4.3-2.2,9.1-3.4,13.9-3.4l157.2,0C903.7,0,906.6,2.9,906.6,6.5L906.6,6.5L906.6,6.5z M65.7,14.4c0,3,2.4,5.5,5.5,5.5h828.9 + c3,0,5.5-2.4,5.5-5.5V6.5c0-3-2.4-5.5-5.5-5.5H742.9c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.2,0.1-0.2,0.1h-79.5 + c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8c-4.2-2.2-8.8-3.3-13.5-3.3H340.1c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.2,0.1-0.2,0.1h-79.5 + c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8C216.3,2.1,211.7,1,207,1L71.2,1c-3,0-5.5,2.4-5.5,5.5L65.7,14.4L65.7,14.4z"/> + <path id="path68-5" d="M858.9,20.3v11.2c0,0.3-0.2,0.5-0.5,0.5H72c-0.3,0-0.5-0.2-0.5-0.5V20.3c0-0.3,0.2-0.5,0.5-0.5h786.4 + C858.7,19.8,858.9,20,858.9,20.3z M857.9,31V20.8H72.5V31H857.9z"/> + <path id="path70-3" d="M1000,37.5v106.2c0,117-94.8,211.9-211.9,211.9l0,0H220.9c-116.8,0-211.8-95.1-211.8-211.9V37.5 + c0-3.6,2.9-6.5,6.5-6.5h978C997.1,31,1000,33.9,1000,37.5L1000,37.5L1000,37.5z M10.1,143.7c0,116.3,94.6,210.9,210.8,210.9h567.2 + C904.4,354.6,999,260,999,143.7V37.5c0-3-2.4-5.5-5.5-5.5h-978c-3,0-5.5,2.4-5.5,5.5v106.2L10.1,143.7L10.1,143.7z"/> + <path id="path98" d="M717.1,6.5v4.2c0,0.6-0.4,1-1,1l0,0h-79.5c-0.6,0-1-0.4-1-1V6.5c0-3.8,3.1-6.9,7-7h67.6 + C714-0.5,717.1,2.6,717.1,6.5z M715.1,9.7V6.5c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2H715.1z"/> + <path id="path100" d="M314.3,6.5v4.2c0,0.6-0.4,1-1,1h-79.5c-0.6,0-1-0.4-1-1V6.5c0-3.8,3.1-6.9,7-7h67.6 + C311.2-0.5,314.3,2.6,314.3,6.5z M312.3,9.7V6.5c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2H312.3z"/> + <path id="path1144" class="st0" d="M997.9,162.4c-3,26.2-8.5,49.9-21,75.1c-10.9,21.9-25.7,41.1-42.5,57.5 + c-33.2,32.3-77.8,53.7-126.7,59c-3.4,0.4-30.5,0.1-72.2,0.3c-158.5,1.1-520.6,0.9-534.4-0.5c-17.7-1.8-36.5-6.1-52.4-12 + c-7.5-2.8-15.8-7.1-23.1-10.7C61.8,299.6,21.2,238.8,12,168.6C9.6,150.4,8.7,35.5,11,33.1c0.1-0.1,2.3-0.9,7-1 + c11.1-0.4,36-0.4,79.4-1.3c32.7-0.7,76.3,0,132.5-0.1c70.3-0.2,160.2,1.6,274.8,1.6c484.5,0,491.2-1.4,492.4,0.9 + c0.2,0.3,1.7,2.5,1.8,5.3c1.1,21.9-0.5,119.2-0.7,120.6L997.9,162.4L997.9,162.4z"/> + <polygon id="polygon80" class="st1" points="470.2,195 470.2,168.3 448.1,181.7 "/> + <polygon id="polygon82" class="st2" points="627.3,181.3 605.6,194.1 605.6,168.5 "/> + <polygon id="polygon84" class="st1" points="538.1,271.6 551.5,249.7 524.7,249.7 "/> + <polygon id="polygon86" class="st3" points="551.6,114.8 524.1,114.8 537.9,89.2 "/> + <path id="path102" d="M139.3,338.3c0,0.3-0.1,0.5-0.3,0.7l-3.4,3.4c-2,2-5.1,2.6-7.8,1.5C50.1,309.1,0.1,231.9,0,146.7l0-51.2 + c0-3.8,3.1-6.9,7-7h2.6c0.6,0,1,0.4,1,1v54.2C10.5,228.2,61,304.5,138.7,337.4c0.3,0.1,0.5,0.4,0.6,0.7L139.3,338.3L139.3,338.3z + M2,146.7c0.1,84.4,49.7,160.9,126.7,195.4c1.9,0.8,4.1,0.4,5.5-1.1l2.4-2.4C58.8,305,8.5,228.3,8.6,143.6V90.4H7c-2.7,0-5,2.2-5,5 + L2,146.7L2,146.7z"/> + <path id="path104" d="M116.1,60v46.9c0,2.2-1.8,4-4,4h-11.7c-2.2,0-4-1.8-4-4V60c0-2.2,1.8-4,4-4h11.7 + C114.3,56,116.1,57.8,116.1,60z M98.5,106.9c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2V60c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2 + V106.9z"/> + <path id="path106" d="M502.9,181.7c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5S502.9,160.4,502.9,181.7 + L502.9,181.7z M428,181.7c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5S428,161.5,428,181.7L428,181.7z"/> + <path id="path108" d="M649.8,181.7c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5S649.8,160.4,649.8,181.7 + L649.8,181.7z M574.9,181.7c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 + C591.2,145.2,574.9,161.5,574.9,181.7L574.9,181.7z"/> + <path id="path110" d="M576.3,108.2c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5l0,0 + C559.1,69.8,576.3,87,576.3,108.2z M501.4,108.2c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 + C517.7,71.8,501.4,88.1,501.4,108.2L501.4,108.2L501.4,108.2z"/> + <path id="path112" d="M576.3,255.1c0,21.2-17.2,38.5-38.5,38.5s-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5l0,0 + C559.1,216.7,576.3,233.9,576.3,255.1z M501.4,255.1c0,20.1,16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5s-16.3-36.5-36.5-36.5l0,0 + C517.7,218.7,501.4,235,501.4,255.1L501.4,255.1L501.4,255.1z"/> + <path id="path114" d="M753.7,105.5v45c0,5.5-4.4,9.9-9.9,9.9h-45c-5.5,0-9.9-4.4-9.9-9.9v-45c0-5.5,4.4-9.9,9.9-9.9h45 + C749.3,95.6,753.7,100.1,753.7,105.5z M690.9,150.5c0,4.4,3.6,7.9,7.9,7.9h45c4.4,0,7.9-3.6,7.9-7.9v-45c0-4.4-3.6-7.9-7.9-7.9h-45 + c-4.4,0-7.9,3.6-7.9,7.9V150.5z"/> + <path id="path116" d="M741.7,128c0,11.3-9.2,20.4-20.4,20.4s-20.4-9.2-20.4-20.4s9.2-20.4,20.4-20.4l0,0 + C732.6,107.6,741.7,116.7,741.7,128z M702.8,128c0,10.2,8.3,18.4,18.4,18.4s18.4-8.3,18.4-18.4s-8.3-18.4-18.4-18.4 + S702.9,117.8,702.8,128z"/> + <path id="path118" d="M260.2,244.8v12.3c0,0.6-0.4,1-1,1l0,0c-39.6-1.7-71.3-33.4-73-73c0-0.3,0.1-0.5,0.3-0.7s0.4-0.3,0.7-0.3 + h12.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9C257.6,238.4,260.2,241.3,260.2,244.8L260.2,244.8z M258.2,256v-11.2 + c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-11.2C190.3,223.4,220.8,253.9,258.2,256L258.2,256z + "/> + <path id="path120" d="M338.9,185L338.9,185c-1.7,39.6-33.4,71.3-73,73c-0.6,0-1-0.4-1-1v-12.3c0-3.5,2.6-6.4,6-6.9 + c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6H338C338.5,184,338.9,184.5,338.9,185L338.9,185z M266.9,256 + c37.4-2.2,67.8-32.6,70-70h-11.2c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9V256L266.9,256z"/> + <path id="path122" d="M338.9,178.3c0,0.6-0.4,1-1,1h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9 + c-3.4-0.5-6-3.4-6-6.9v-12.3c0-0.6,0.4-1,1-1l0,0C305.5,107,337.2,138.7,338.9,178.3L338.9,178.3L338.9,178.3z M266.9,118.6 + c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2c-2.2-37.4-32.6-67.8-70-70V118.6z"/> + <path id="path124" d="M260.2,106.3v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-12.3 + c-0.3,0-0.5-0.1-0.7-0.3s-0.3-0.5-0.3-0.7c1.7-39.6,33.4-71.3,73-73C259.7,105.3,260.2,105.7,260.2,106.3L260.2,106.3L260.2,106.3z + M188.2,177.3h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2 + C220.8,109.5,190.3,140,188.2,177.3L188.2,177.3L188.2,177.3z"/> + <path id="path126" d="M339,181.7c0,1.2,0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3 + c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1c-1.1,0.1-2.2,0.1-3.4,0.1s-2.3,0-3.4-0.1 + c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3 + c-0.5,0-1-0.4-1-0.9c-0.1-2.3-0.1-4.5,0-6.8c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6 + c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1c1.1-0.1,2.2-0.1,3.4-0.1s2.3,0,3.4,0.1c0.5,0,0.9,0.5,0.9,1v12.3 + c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3c0.5,0,1,0.4,1,0.9C339,179.4,339,180.5,339,181.7 + L339,181.7z M337,184c0.1-1.5,0.1-3.1,0-4.7h-11.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3 + h-4.6v11.4c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3c-0.1,1.5-0.1,3.1,0,4.7h11.3 + c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9v11.3h4.6v-11.3c0-3.5,2.6-6.4,6-6.9 + c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6L337,184L337,184z"/> + <path id="path179" class="st4" d="M574.3,261.6c-3.2,14.7-12.7,24.9-27.1,29c-3.4,1-6.3,1.2-11.7,0.9c-6.5-0.3-7.8-0.7-13.5-3.5 + c-22.9-11.4-27.9-40.7-10-58.6c19.4-19.4,51.9-12,60.8,13.9C574.5,248.5,575.3,257.1,574.3,261.6L574.3,261.6L574.3,261.6z + M538,249c-7.3,0-13.4,0.2-13.4,0.4c0,0.9,13.2,23.3,13.5,23c0.7-0.7,13.1-22.2,13.2-22.8C551.3,249.2,545.4,249,538,249L538,249 + L538,249z"/> + <path id="path181" class="st4" d="M500.6,187.1c-0.9,6.9-5,15-10.4,20.5c-14.5,14.6-36.9,14.6-51.4-0.1c-22.9-23-7-62.3,25.4-62.3 + c10.4,0,18.8,3.5,26,10.7C498.4,164.3,502.1,175.2,500.6,187.1L500.6,187.1L500.6,187.1z M469.9,168.5 + c-2.4,0.9-22.4,12.8-22.4,13.3c0,0.4,9,5.9,19.9,12l3.6,2v-13.9C471,169.7,470.9,168.1,469.9,168.5L469.9,168.5L469.9,168.5z"/> + <path id="path185" class="st4" d="M647,190.4c-3.5,12.9-13.7,23.1-26.6,26.5c-22.7,5.9-45.2-11.6-45.3-35.2 + c0-7.4,0.9-11.3,4.5-18.1c8.8-16.9,30.4-23.9,47.9-15.4C643,155.8,651.4,173.9,647,190.4L647,190.4L647,190.4z M616.9,174.2 + c-6.3-3.6-11.6-6.5-11.8-6.3s-0.3,6.4-0.1,13.8l0.2,13.5l11.6-6.7c6.4-3.7,11.6-6.9,11.6-7.2C628.4,181.1,623.2,177.8,616.9,174.2 + L616.9,174.2z"/> + <path id="path187" class="st4" d="M574.2,111.7c0,0.3-0.3,1.6-0.5,2.9c-2.2,11.2-9.7,20.9-20.1,26.2c-5.6,2.8-12.2,4.2-17.9,3.8 + c-14.8-1.1-27.7-11.3-32.5-25.6c-2.9-8.8-2.2-18,2.2-26.7s12.1-15.5,21.3-18.6c15.3-5.3,32.2,0.7,41.6,14.7c4,6,6.3,13.4,6.2,20.2 + C574.3,109.9,574.3,111.4,574.2,111.7L574.2,111.7L574.2,111.7z M551.1,112.4c-0.4-0.7-1.6-3-2.8-5.1c-4.8-8.7-9.2-15.6-10-16.2 + c-0.4-0.3-0.5-0.3-0.9,0c-1.3,0.9-13.5,21.5-13.5,22.9c0,0.3,0.1,0.6,0.2,0.7c0.5,0.4,4.8,0.6,13.9,0.6c9.1,0,13-0.2,13.5-0.6 + C551.9,114.3,551.8,113.8,551.1,112.4L551.1,112.4L551.1,112.4z"/> + <path id="path189" class="st5" d="M739.1,131.6c-0.2,1-1,3-1.6,4.4c-1,2.1-1.6,2.9-3.5,4.8c-1.9,1.9-2.7,2.5-4.8,3.5 + c-5.5,2.7-10.6,2.7-16.1,0c-2.1-1-2.8-1.6-4.8-3.5c-3.6-3.6-5.3-7.7-5.3-12.7c0-8.4,5.7-15.7,13.9-17.8c2-0.5,6.2-0.5,8.3,0 + c6.3,1.5,11.5,6.3,13.4,12.7C739.4,125.4,739.6,129.2,739.1,131.6L739.1,131.6L739.1,131.6z"/> + <path id="path191" class="st4" d="M751.3,152.4c-0.4,1.6-1.3,3-2.6,4.1c-2.1,1.8-0.9,1.7-27.4,1.7s-25.3,0.1-27.5-1.9 + c-0.7-0.6-1.5-1.7-1.9-2.5l-0.7-1.5v-48.3l0.9-1.8c0.6-1.3,1.2-2,2.1-2.7c2.3-1.8,1.3-1.7,27.8-1.6c23.8,0.1,23.9,0.1,25.1,0.6 + c1.6,0.7,3.1,2.3,3.9,3.9l0.7,1.3l0,23.8C751.6,145.6,751.5,151.4,751.3,152.4L751.3,152.4L751.3,152.4z M741.6,125.1 + c-1.5-10.6-10.7-18.1-21.4-17.5c-3.4,0.2-5.3,0.7-8.1,2c-9.3,4.6-13.7,15.5-10.2,25.3c1,2.8,2.2,4.7,4.3,7c2.8,3,6.3,5.1,10.3,6 + c2.2,0.5,7.2,0.5,9.4,0c3.9-0.9,7.4-3,10.2-6c2.1-2.2,3.3-4.2,4.3-6.7C741.7,131.9,742.1,128.5,741.6,125.1L741.6,125.1 + L741.6,125.1z"/> + <path id="path1082" class="st6" d="M330.4,183.8c-6,0-6.5,0.1-7.9,0.7c-2.1,1.1-3.4,2.9-3.9,5.6c-2.7,15.1-10.1,27.5-21.9,36.5 + c-6.5,5-15.1,8.8-23.2,10.4c-4.3,0.8-5.7,1.4-7,3c-1.5,1.8-1.8,3.3-1.8,10v5.9h-4.2v-6.3c0-6.1,0-6.3-0.9-8 + c-1.2-2.4-2.7-3.3-7.3-4.2c-23.2-4.6-41-22.4-45.4-45.6c-0.8-4.2-1.6-5.6-3.8-6.9c-1.5-0.9-1.6-0.9-8.1-1l-6.6-0.1v-4.2h6.3 + c6.2,0,6.3,0,8-0.9c2.6-1.3,3.4-2.6,4.4-7.6c3.8-19.4,17-35.1,35.4-42.3c2.7-1,5.9-1.9,12-3.3c2.6-0.6,4.2-1.7,5.2-3.6 + c0.6-1.1,0.7-2,0.8-7.9l0.1-6.6h4.2v6.3c0,6.1,0,6.3,0.9,8c1.1,2.2,2.9,3.4,5.9,3.9c23.8,4.1,42.3,22.2,46.8,46 + c0.8,4.2,1.7,5.7,4.1,7c1.5,0.8,1.9,0.8,8,0.9l6.4,0.1v4.2L330.4,183.8L330.4,183.8z"/> + <path id="path1084" class="st7" d="M257.3,255.8c-0.5-0.1-1.9-0.3-3.2-0.4c-3.9-0.4-10.1-1.7-14.8-3.3c-24.1-8-42.7-28.1-48.9-52.7 + c-0.8-3.1-1.6-7.6-1.9-11.3l-0.2-2h5.9c8.5,0,9.1,0.4,10.4,6.5c3.7,18.4,15.2,33.7,31.9,41.9c5.1,2.6,10.1,4.1,17,5.5 + c2.4,0.5,4,1.8,4.4,3.7c0.2,0.7,0.3,3.8,0.3,6.8v5.5L257.3,255.8L257.3,255.8z"/> + <path id="path1086" class="st7" d="M336.4,189.8c-3,27-21.4,50.8-46.9,60.9c-6,2.4-13.4,4.2-18.7,4.7c-1.3,0.1-2.7,0.3-3.1,0.4 + l-0.8,0.2l0.1-6.3c0.1-5.8,0.2-6.4,0.8-7.2c1.2-1.6,2.2-2,6.2-2.9c5.8-1.2,9.4-2.4,14.8-5.2c5.7-2.9,9.8-5.7,14.2-9.9 + c9.1-8.6,15.2-19.7,17.5-31.7c0.7-3.6,1.2-4.6,2.7-5.8c0.8-0.6,1.4-0.7,7.2-0.8c5.9-0.1,6.3-0.1,6.3,0.5 + C336.7,187,336.6,188.4,336.4,189.8L336.4,189.8L336.4,189.8z"/> + <path id="path1088" class="st7" d="M257.7,119.9c-0.9,2.4-1.6,2.8-6.5,3.8c-18.2,3.7-33.5,15.4-41.6,32c-2.4,4.8-3.7,8.6-4.7,13.5 + c-1,4.7-1.6,6.2-2.9,7.1c-1.1,0.7-1.4,0.7-7.4,0.7c-3.5,0-6.3-0.1-6.3-0.3s0.2-1.9,0.5-4c1.5-12,5.6-22.9,12.4-32.8 + c3-4.4,5.6-7.4,9.9-11.6c9.6-9.3,20.7-15.5,33.5-18.8c3.6-0.9,10.9-2.1,12.9-2.1c0.6,0,0.6,0.3,0.6,5.6 + C258.1,116.9,258,119.1,257.7,119.9L257.7,119.9L257.7,119.9z"/> + <path id="path1090" class="st7" d="M330.5,177.3c-5.8-0.1-6.4-0.2-7.2-0.8c-1.6-1.2-2-2.2-2.9-6.3c-4.7-23.5-23.3-41.9-47-46.4 + c-3.5-0.7-4.5-1.1-5.6-2.7c-0.6-0.8-0.7-1.4-0.8-7.2l-0.1-6.3l1.7,0.2c10.5,1.2,18,3.3,26.4,7.5c22.6,11.2,38.1,32.9,41.3,58 + c0.3,2.1,0.5,3.9,0.5,4S333.9,177.3,330.5,177.3L330.5,177.3L330.5,177.3z"/> + <path id="path1092" class="st4" d="M113.5,108c-0.6,0.5-1.8,0.7-7.2,0.7c-4.5,0-6.7-0.2-7-0.5c-0.4-0.4-0.5-6.4-0.5-24.6 + c0-21.3,0.1-24.2,0.7-24.8c0.6-0.5,1.8-0.7,7-0.7c6.1,0,6.4,0,7,1c0.6,0.8,0.7,3.9,0.7,24.6S114,107.4,113.5,108z"/> + <path id="path1094" class="st8" d="M134.2,340.6c-2.8,2.4-3.7,2.2-12.6-2.3c-21.7-10.8-39.6-23.6-56.5-40.5 + c-31.4-31.4-52.2-71.6-59.8-115.3c-2.6-15.1-2.7-16.5-2.9-54L2.3,93.7l1.2-1.4c0.9-1,1.7-1.4,3-1.6l1.8-0.2l0.2,33.8 + c0.2,36.2,0.3,38.8,2.7,53.3c6.2,38.4,22,72.9,47.3,103.3c5.7,6.8,18.3,19.5,25.2,25.2c10,8.4,21.7,16.5,32.4,22.5 + c5.4,3.1,18.7,9.6,19.5,9.6C136.4,338.3,136,339.1,134.2,340.6L134.2,340.6L134.2,340.6z"/> + <path id="path1150" class="st9" d="M616,21.1c-216,0-634,0-526-0.1c108-0.1,614.3-0.3,722.4-0.1c29.2,0,43.2,0.1,45,0.1 + C862.4,21.1,773.6,21.1,616,21.1L616,21.1L616,21.1z"/> + <path id="path1152" class="st4" d="M903.2,19c-2.1,1.5-44.2,0.8-417.6,0.8S70.3,20.6,68.2,19.1c-2-1.4-2.2-3.4-2.2-8.5 + c0-1.8-0.1-3.8,0.2-5.4c0.3-1.4,2-3.2,2.4-3.5c0.5-0.5,4.3-0.3,16.4-0.4c11.3-0.1,29,0.2,55.3,0.2c44.7,0,61.8-0.6,69.8-0.1 + c4.4,0.3,6.1,1.2,8.1,1.9c3.1,1.2,8.2,3.9,10.9,5.6l5,3.2l40.4-0.4c37.6-0.4,39,0.2,41.7-1.6c1.5-1.1,6.1-3,9.3-4.6l5.8-3l7.9-1.3 + l19.8,0.1l15.8,0.1l97.9,0.2c93.1,0.2,126.7-1.2,141.1,0.3c4.2,0.4,6.5,1.6,8.5,2.3c2.7,0.9,4.9,2.1,7.8,3.9l6.1,3.7l15.6-0.4 + l25.1-0.1c26.5-0.1,40.4-0.6,40.9-1.3c0.4-0.6,4-2.7,8.5-4.3l9-3.3l9-1.1l15.9,0.2l57.9,0.2c61.9,0.2,84.5-0.4,85.5,0.5 + c0.9,0.8,2.4,4.5,2.4,8.4C905.8,15.4,905.1,17.6,903.2,19L903.2,19L903.2,19z"/> + <path id="path1154" class="st4" d="M464.9,30.8l-5.2,0l-387-0.4v-8.5h392.2l393-1.1l0.2,5.3l-0.2,4.9L464.9,30.8L464.9,30.8z"/> + <path id="path1156" class="st0" d="M273.3,9.7h-38.6v-3c0-1.7,0.9-3.6,2.1-4.3c2.8-1.7,70.3-1.7,73.1,0c1.2,0.7,2.1,2.6,2.1,4.3v3 + H273.3L273.3,9.7z"/> + <path id="path1158" class="st0" d="M676.2,9.7h-38.7v-3c0-1.8,0.9-3.6,2.1-4.3c2.8-1.7,70.3-1.7,73.2,0c1.2,0.7,2.1,2.6,2.1,4.3v3 + H676.2L676.2,9.7z"/> +</g> +</svg> diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg new file mode 100644 index 00000000..8097762d --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg @@ -0,0 +1,341 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 1000 1000.2" style="enable-background:new 0 0 1000 1000.2;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#00BBDB;stroke:#000000;} + .st1{fill:#333333;stroke:#000000;stroke-width:0.93;} + .st2{fill:#333333;stroke:#000000;stroke-width:0.96;} + .st3{fill:#333333;stroke:#000000;stroke-width:0.85;} + .st4{fill:#1A1A1A;stroke:#000000;} + .st5{fill:#333333;stroke:#000000;} + .st6{fill:#1A1A1A;stroke:#1A1A1A;} + .st7{fill:#1A1A1A;stroke:#4D4D4D;} + .st8{stroke:#4D4D4D;} + .st9{stroke:#000000;} + .st10{opacity:0.1;} + .st11{fill:#FF5F55;} + .st12{fill:#FF5F53;} + .st13{fill:#FFFFFF;} + .st14{fill:#999595;stroke:#000000;stroke-width:2.39;stroke-linecap:round;stroke-linejoin:round;} + .st15{fill:#3A3D40;stroke:#000000;stroke-width:2.73;stroke-linecap:round;stroke-linejoin:round;} +</style> +<g id="layer1"> + <g id="g344"> + <path id="path66-7" d="M349.1,906.6h-7.9c-3.6,0-6.4-2.9-6.5-6.5V71.2c0-3.6,2.9-6.4,6.5-6.5h7.9c3.6,0,6.4,2.9,6.5,6.5V207 + c0,4.9-1.2,9.6-3.4,14l-6.7,13v79.2l6.7,13c2.2,4.3,3.4,9.1,3.4,13.9v269.7c0,4.9-1.2,9.6-3.4,14l-6.7,13V716l6.7,13 + c2.2,4.3,3.4,9.1,3.4,13.9v157.2C355.6,903.7,352.7,906.6,349.1,906.6L349.1,906.6L349.1,906.6z M341.2,65.7c-3,0-5.5,2.4-5.5,5.5 + v828.9c0,3,2.4,5.5,5.5,5.5h7.9c3,0,5.5-2.4,5.5-5.5V742.9c0-4.7-1.1-9.3-3.3-13.5l-6.8-13.1c0-0.1-0.1-0.2-0.1-0.2v-79.5 + c0-0.1,0-0.2,0.1-0.2l6.8-13.1c2.2-4.2,3.3-8.8,3.3-13.5V340.1c0-4.7-1.1-9.3-3.3-13.5l-6.8-13.1c0-0.1-0.1-0.2-0.1-0.2v-79.5 + c0-0.1,0-0.2,0.1-0.2l6.8-13.1c2.2-4.2,3.3-8.8,3.3-13.5V71.2c0-3-2.4-5.5-5.5-5.5L341.2,65.7L341.2,65.7z"/> + <path id="path68-5" d="M335.3,858.9h-11.2c-0.3,0-0.5-0.2-0.5-0.5V72c0-0.3,0.2-0.5,0.5-0.5h11.2c0.3,0,0.5,0.2,0.5,0.5v786.4 + C335.8,858.7,335.6,858.9,335.3,858.9z M324.6,857.9h10.2V72.5h-10.2V857.9z"/> + <path id="path70-3" d="M318.1,1000H211.9C94.9,1000,0,905.2,0,788.1l0,0V220.9C0,104.1,95.1,9.1,211.9,9.1h106.2 + c3.6,0,6.5,2.9,6.5,6.5v978C324.6,997.1,321.7,1000,318.1,1000L318.1,1000L318.1,1000z M211.9,10.1C95.6,10.1,1,104.7,1,220.9 + v567.2C1,904.4,95.6,999,211.9,999h106.2c3,0,5.5-2.4,5.5-5.5v-978c0-3-2.4-5.5-5.5-5.5H211.9L211.9,10.1L211.9,10.1z"/> + <path id="path98" d="M349.1,717.1h-4.2c-0.6,0-1-0.4-1-1l0,0v-79.5c0-0.6,0.4-1,1-1h4.2c3.8,0,6.9,3.1,7,7v67.6 + C356.1,714,353,717.1,349.1,717.1z M345.9,715.1h3.2c2.7,0,5-2.2,5-5v-67.6c0-2.7-2.2-5-5-5h-3.2V715.1z"/> + <path id="path100" d="M349.1,314.3h-4.2c-0.6,0-1-0.4-1-1v-79.5c0-0.6,0.4-1,1-1h4.2c3.8,0,6.9,3.1,7,7v67.6 + C356.1,311.2,353,314.3,349.1,314.3z M345.9,312.3h3.2c2.7,0,5-2.2,5-5v-67.6c0-2.7-2.2-5-5-5h-3.2V312.3z"/> + <path id="path1144" class="st0" d="M193.2,997.9c-26.2-3-49.9-8.5-75.1-21C96.2,966,77,951.2,60.7,934.4 + C28.4,901.1,7,856.6,1.7,807.7c-0.4-3.4-0.1-30.5-0.3-72.2C0.3,576.9,0.4,214.9,1.8,201c1.8-17.7,6.1-36.5,12-52.4 + c2.8-7.5,7.1-15.8,10.7-23.1C55.9,61.8,116.8,21.2,187,12c18.2-2.4,133.1-3.3,135.5-0.9c0.1,0.1,0.9,2.3,1,7 + c0.4,11.1,0.4,36,1.3,79.4c0.7,32.7,0,76.3,0.1,132.5c0.2,70.3-1.6,160.2-1.6,274.8c0,484.5,1.4,491.2-0.9,492.4 + c-0.3,0.2-2.5,1.7-5.3,1.8c-21.9,1.1-119.2-0.5-120.6-0.7L193.2,997.9L193.2,997.9z"/> + <polygon id="polygon80" class="st1" points="173.9,448.1 160.6,470.2 187.3,470.2 "/> + <polygon id="polygon82" class="st2" points="187.1,605.6 174.3,627.3 161.5,605.6 "/> + <polygon id="polygon84" class="st1" points="105.9,524.7 84,538.1 105.9,551.5 "/> + <polygon id="polygon86" class="st3" points="266.4,537.9 240.8,551.6 240.8,524.1 "/> + <path id="path102" d="M17.3,139.3c-0.3,0-0.5-0.1-0.7-0.3l-3.4-3.4c-2-2-2.6-5.1-1.5-7.8C46.5,50.1,123.7,0.1,208.9,0h51.2 + c3.8,0,6.9,3.1,7,7v2.6c0,0.6-0.4,1-1,1h-54.2C127.4,10.5,51.1,61,18.2,138.7c-0.1,0.3-0.4,0.5-0.7,0.6L17.3,139.3L17.3,139.3z + M208.9,2C124.5,2.1,48,51.7,13.5,128.7c-0.8,1.9-0.4,4.1,1.1,5.5l2.4,2.4C50.6,58.8,127.3,8.5,212,8.6h53.2V7c0-2.7-2.2-5-5-5 + L208.9,2L208.9,2z"/> + <path id="path104" d="M295.6,116.1h-46.9c-2.2,0-4-1.8-4-4v-11.7c0-2.2,1.8-4,4-4h46.9c2.2,0,4,1.8,4,4v11.7 + C299.6,114.3,297.8,116.1,295.6,116.1z M248.7,98.5c-1.1,0-2,0.9-2,2v11.7c0,1.1,0.9,2,2,2h46.9c1.1,0,2-0.9,2-2v-11.7 + c0-1.1-0.9-2-2-2H248.7z"/> + <path id="path106" d="M173.9,502.9c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5S195.2,502.9,173.9,502.9 + L173.9,502.9z M173.9,428c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5S194.1,428,173.9,428L173.9,428z" + /> + <path id="path108" d="M173.9,649.8c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5S195.2,649.8,173.9,649.8 + L173.9,649.8z M173.9,574.9c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 + C210.4,591.2,194.1,574.9,173.9,574.9L173.9,574.9z"/> + <path id="path110" d="M247.4,576.3c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5l0,0 + C285.8,559.1,268.6,576.3,247.4,576.3z M247.4,501.4c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 + C283.8,517.7,267.5,501.4,247.4,501.4L247.4,501.4L247.4,501.4z"/> + <path id="path112" d="M100.5,576.3c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5l0,0 + C138.9,559.1,121.7,576.3,100.5,576.3z M100.5,501.4c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5s36.5-16.3,36.5-36.5l0,0 + C136.9,517.7,120.6,501.4,100.5,501.4L100.5,501.4L100.5,501.4z"/> + <path id="path114" d="M250.1,753.7h-45c-5.5,0-9.9-4.4-9.9-9.9v-45c0-5.5,4.4-9.9,9.9-9.9h45c5.5,0,9.9,4.4,9.9,9.9v45 + C260,749.3,255.5,753.7,250.1,753.7z M205.1,690.9c-4.4,0-7.9,3.6-7.9,7.9v45c0,4.4,3.6,7.9,7.9,7.9h45c4.4,0,7.9-3.6,7.9-7.9v-45 + c0-4.4-3.6-7.9-7.9-7.9H205.1z"/> + <path id="path116" d="M227.6,741.7c-11.3,0-20.4-9.2-20.4-20.4s9.2-20.4,20.4-20.4s20.4,9.2,20.4,20.4l0,0 + C248,732.6,238.9,741.7,227.6,741.7z M227.6,702.8c-10.2,0-18.4,8.3-18.4,18.4s8.3,18.4,18.4,18.4s18.4-8.3,18.4-18.4 + S237.8,702.9,227.6,702.8z"/> + <path id="path118" d="M110.8,260.2H98.5c-0.6,0-1-0.4-1-1l0,0c1.7-39.6,33.4-71.3,73-73c0.3,0,0.5,0.1,0.7,0.3s0.3,0.4,0.3,0.7 + v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9C117.2,257.6,114.3,260.2,110.8,260.2L110.8,260.2z M99.6,258.2h11.2 + c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2C132.2,190.3,101.7,220.8,99.6,258.2L99.6,258.2 + z"/> + <path id="path120" d="M170.6,338.9L170.6,338.9c-39.6-1.7-71.3-33.4-73-73c0-0.6,0.4-1,1-1h12.3c3.5,0,6.4,2.6,6.9,6 + c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9V338C171.6,338.5,171.1,338.9,170.6,338.9L170.6,338.9z M99.6,266.9 + c2.2,37.4,32.6,67.8,70,70v-11.2c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3H99.6L99.6,266.9z" + /> + <path id="path122" d="M177.3,338.9c-0.6,0-1-0.4-1-1v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6 + h12.3c0.6,0,1,0.4,1,1l0,0C248.6,305.5,216.9,337.2,177.3,338.9L177.3,338.9L177.3,338.9z M237,266.9c-2.5,0-4.6,1.8-4.9,4.3 + c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v11.2c37.4-2.2,67.8-32.6,70-70H237z"/> + <path id="path124" d="M249.3,260.2H237c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-12.3 + c0-0.3,0.1-0.5,0.3-0.7s0.5-0.3,0.7-0.3c39.6,1.7,71.3,33.4,73,73C250.3,259.7,249.9,260.2,249.3,260.2L249.3,260.2L249.3,260.2z + M178.3,188.2v11.2c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2 + C246.1,220.8,215.6,190.3,178.3,188.2L178.3,188.2L178.3,188.2z"/> + <path id="path126" d="M173.9,339c-1.2,0-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 + c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3H98.5c-0.5,0-1-0.4-1-0.9c-0.1-1.1-0.1-2.2-0.1-3.4s0-2.3,0.1-3.4 + c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1 + c2.3-0.1,4.5-0.1,6.8,0c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3 + c0.5,0,1,0.4,1,0.9c0.1,1.1,0.1,2.2,0.1,3.4s0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9H237c-2.5,0-4.6,1.8-4.9,4.3 + c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1C176.2,339,175.1,339,173.9,339L173.9,339z + M171.6,337c1.5,0.1,3.1,0.1,4.7,0v-11.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h11.3v-4.6H237 + c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3c-1.5-0.1-3.1-0.1-4.7,0v11.3 + c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6H99.4v4.6h11.3c3.5,0,6.4,2.6,6.9,6 + c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9L171.6,337L171.6,337z"/> + <path id="path179" class="st4" d="M94,574.3c-14.7-3.2-24.9-12.7-29-27.1c-1-3.4-1.2-6.3-0.9-11.7c0.3-6.5,0.7-7.8,3.5-13.5 + c11.4-22.9,40.7-27.9,58.6-10c19.4,19.4,12,51.9-13.9,60.8C107.1,574.5,98.5,575.3,94,574.3L94,574.3L94,574.3z M106.6,538 + c0-7.3-0.2-13.4-0.4-13.4c-0.9,0-23.3,13.2-23,13.5c0.7,0.7,22.2,13.1,22.8,13.2C106.4,551.3,106.6,545.4,106.6,538L106.6,538 + L106.6,538z"/> + <path id="path181" class="st4" d="M168.5,500.6c-6.9-0.9-15-5-20.5-10.4c-14.6-14.5-14.6-36.9,0.1-51.4c23-22.9,62.3-7,62.3,25.4 + c0,10.4-3.5,18.8-10.7,26C191.3,498.4,180.4,502.1,168.5,500.6L168.5,500.6L168.5,500.6z M187.1,469.9 + c-0.9-2.4-12.8-22.4-13.3-22.4c-0.4,0-5.9,9-12,19.9l-2,3.6h13.9C185.9,471,187.5,470.9,187.1,469.9L187.1,469.9L187.1,469.9z"/> + <path id="path185" class="st4" d="M165.2,647c-12.9-3.5-23.1-13.7-26.5-26.6c-5.9-22.7,11.6-45.2,35.2-45.3 + c7.4,0,11.3,0.9,18.1,4.5c16.9,8.8,23.9,30.4,15.4,47.9C199.8,643,181.7,651.4,165.2,647L165.2,647L165.2,647z M181.4,616.9 + c3.6-6.3,6.5-11.6,6.3-11.8s-6.4-0.3-13.8-0.1l-13.5,0.2l6.7,11.6c3.7,6.4,6.9,11.6,7.2,11.6C174.5,628.4,177.8,623.2,181.4,616.9 + L181.4,616.9z"/> + <path id="path187" class="st4" d="M243.9,574.2c-0.3,0-1.6-0.3-2.9-0.5c-11.2-2.2-20.9-9.7-26.2-20.1c-2.8-5.6-4.2-12.2-3.8-17.9 + c1.1-14.8,11.3-27.7,25.6-32.5c8.8-2.9,18-2.2,26.7,2.2s15.5,12.1,18.6,21.3c5.3,15.3-0.7,32.2-14.7,41.6c-6,4-13.4,6.3-20.2,6.2 + C245.7,574.3,244.2,574.3,243.9,574.2L243.9,574.2L243.9,574.2z M243.2,551.1c0.7-0.4,3-1.6,5.1-2.8c8.7-4.8,15.6-9.2,16.2-10 + c0.3-0.4,0.3-0.5,0-0.9c-0.9-1.3-21.5-13.5-22.9-13.5c-0.3,0-0.6,0.1-0.7,0.2c-0.4,0.5-0.6,4.8-0.6,13.9c0,9.1,0.2,13,0.6,13.5 + C241.3,551.9,241.8,551.8,243.2,551.1L243.2,551.1L243.2,551.1z"/> + <path id="path189" class="st5" d="M224,739.1c-1-0.2-3-1-4.4-1.6c-2.1-1-2.9-1.6-4.8-3.5c-1.9-1.9-2.5-2.7-3.5-4.8 + c-2.7-5.5-2.7-10.6,0-16.1c1-2.1,1.6-2.8,3.5-4.8c3.6-3.6,7.7-5.3,12.7-5.3c8.4,0,15.7,5.7,17.8,13.9c0.5,2,0.5,6.2,0,8.3 + c-1.5,6.3-6.3,11.5-12.7,13.4C230.2,739.4,226.4,739.6,224,739.1L224,739.1L224,739.1z"/> + <path id="path191" class="st4" d="M203.2,751.3c-1.6-0.4-3-1.3-4.1-2.6c-1.8-2.1-1.7-0.9-1.7-27.4s-0.1-25.3,1.9-27.5 + c0.6-0.7,1.7-1.5,2.5-1.9l1.5-0.7h48.3l1.8,0.9c1.3,0.6,2,1.2,2.7,2.1c1.8,2.3,1.7,1.3,1.6,27.8c-0.1,23.8-0.1,23.9-0.6,25.1 + c-0.7,1.6-2.3,3.1-3.9,3.9l-1.3,0.7l-23.8,0C210,751.6,204.2,751.5,203.2,751.3L203.2,751.3L203.2,751.3z M230.5,741.6 + c10.6-1.5,18.1-10.7,17.5-21.4c-0.2-3.4-0.7-5.3-2-8.1c-4.6-9.3-15.5-13.7-25.3-10.2c-2.8,1-4.7,2.2-7,4.3c-3,2.8-5.1,6.3-6,10.3 + c-0.5,2.2-0.5,7.2,0,9.4c0.9,3.9,3,7.4,6,10.2c2.2,2.1,4.2,3.3,6.7,4.3C223.7,741.7,227.1,742.1,230.5,741.6L230.5,741.6 + L230.5,741.6z"/> + <path id="path1082" class="st6" d="M171.8,330.4c0-6-0.1-6.5-0.7-7.9c-1.1-2.1-2.9-3.4-5.6-3.9c-15.1-2.7-27.5-10.1-36.5-21.9 + c-5-6.5-8.8-15.1-10.4-23.2c-0.8-4.3-1.4-5.7-3-7c-1.8-1.5-3.3-1.8-10-1.8h-5.9v-4.2h6.3c6.1,0,6.3,0,8-0.9 + c2.4-1.2,3.3-2.7,4.2-7.3c4.6-23.2,22.4-41,45.6-45.4c4.2-0.8,5.6-1.6,6.9-3.8c0.9-1.5,0.9-1.6,1-8.1l0.1-6.6h4.2v6.3 + c0,6.2,0,6.3,0.9,8c1.3,2.6,2.6,3.4,7.6,4.4c19.4,3.8,35.1,17,42.3,35.4c1,2.7,1.9,5.9,3.3,12c0.6,2.6,1.7,4.2,3.6,5.2 + c1.1,0.6,2,0.7,7.9,0.8l6.6,0.1v4.2h-6.3c-6.1,0-6.3,0-8,0.9c-2.2,1.1-3.4,2.9-3.9,5.9c-4.1,23.8-22.2,42.3-46,46.8 + c-4.2,0.8-5.7,1.7-7,4.1c-0.8,1.5-0.8,1.9-0.9,8l-0.1,6.4h-4.2L171.8,330.4L171.8,330.4z"/> + <path id="path1084" class="st7" d="M99.8,257.3c0.1-0.5,0.3-1.9,0.4-3.2c0.4-3.9,1.7-10.1,3.3-14.8c8-24.1,28.1-42.7,52.7-48.9 + c3.1-0.8,7.6-1.6,11.3-1.9l2-0.2v5.9c0,8.5-0.4,9.1-6.5,10.4c-18.4,3.7-33.7,15.2-41.9,31.9c-2.6,5.1-4.1,10.1-5.5,17 + c-0.5,2.4-1.8,4-3.7,4.4c-0.7,0.2-3.8,0.3-6.8,0.3h-5.5L99.8,257.3L99.8,257.3z"/> + <path id="path1086" class="st7" d="M165.8,336.4c-27-3-50.8-21.4-60.9-46.9c-2.4-6-4.2-13.4-4.7-18.7c-0.1-1.3-0.3-2.7-0.4-3.1 + l-0.2-0.8l6.3,0.1c5.8,0.1,6.4,0.2,7.2,0.8c1.6,1.2,2,2.2,2.9,6.2c1.2,5.8,2.4,9.4,5.2,14.8c2.9,5.7,5.7,9.8,9.9,14.2 + c8.6,9.1,19.7,15.2,31.7,17.5c3.6,0.7,4.6,1.2,5.8,2.7c0.6,0.8,0.7,1.4,0.8,7.2c0.1,5.9,0.1,6.3-0.5,6.3 + C168.6,336.7,167.2,336.6,165.8,336.4L165.8,336.4L165.8,336.4z"/> + <path id="path1088" class="st7" d="M235.7,257.7c-2.4-0.9-2.8-1.6-3.8-6.5c-3.7-18.2-15.4-33.5-32-41.6c-4.8-2.4-8.6-3.7-13.5-4.7 + c-4.7-1-6.2-1.6-7.1-2.9c-0.7-1.1-0.7-1.4-0.7-7.4c0-3.5,0.1-6.3,0.3-6.3s1.9,0.2,4,0.5c12,1.5,22.9,5.6,32.8,12.4 + c4.4,3,7.4,5.6,11.6,9.9c9.3,9.6,15.5,20.7,18.8,33.5c0.9,3.6,2.1,10.9,2.1,12.9c0,0.6-0.3,0.6-5.6,0.6 + C238.7,258.1,236.5,258,235.7,257.7L235.7,257.7L235.7,257.7z"/> + <path id="path1090" class="st7" d="M178.3,330.5c0.1-5.8,0.2-6.4,0.8-7.2c1.2-1.6,2.2-2,6.3-2.9c23.5-4.7,41.9-23.3,46.4-47 + c0.7-3.5,1.1-4.5,2.7-5.6c0.8-0.6,1.4-0.7,7.2-0.8l6.3-0.1l-0.2,1.7c-1.2,10.5-3.3,18-7.5,26.4c-11.2,22.6-32.9,38.1-58,41.3 + c-2.1,0.3-3.9,0.5-4,0.5S178.3,333.9,178.3,330.5L178.3,330.5L178.3,330.5z"/> + <path id="path1092" class="st4" d="M247.6,113.5c-0.5-0.6-0.7-1.8-0.7-7.2c0-4.5,0.2-6.7,0.5-7c0.4-0.4,6.4-0.5,24.6-0.5 + c21.3,0,24.2,0.1,24.8,0.7c0.5,0.6,0.7,1.8,0.7,7c0,6.1,0,6.4-1,7c-0.8,0.6-3.9,0.7-24.6,0.7S248.2,114,247.6,113.5z"/> + <path id="path1094" class="st8" d="M15,134.2c-2.3-2.8-2.2-3.7,2.3-12.6C28,99.9,40.9,82,57.8,65.1C89.1,33.7,129.4,12.8,173,5.3 + c15.1-2.6,16.5-2.7,54-2.9l34.8-0.2l1.4,1.2c1,0.9,1.4,1.7,1.6,3l0.2,1.8l-33.8,0.2C195,8.6,192.5,8.8,178,11.1 + c-38.4,6.2-72.9,22-103.3,47.3c-6.8,5.7-19.5,18.3-25.2,25.2c-8.4,10-16.5,21.7-22.5,32.4c-3.1,5.4-9.6,18.7-9.6,19.5 + C17.3,136.4,16.5,136,15,134.2L15,134.2L15,134.2z"/> + <path id="path1150" class="st9" d="M334.4,616c0-216,0-634,0.1-526c0.1,108,0.3,614.3,0.1,722.4c0,29.2-0.1,43.2-0.1,45 + C334.5,862.4,334.5,773.6,334.4,616L334.4,616L334.4,616z"/> + <path id="path1152" class="st4" d="M336.6,903.2c-1.5-2.1-0.8-44.2-0.8-417.6S335,70.3,336.5,68.2c1.4-2,3.4-2.2,8.5-2.2 + c1.8,0,3.8-0.1,5.4,0.2c1.4,0.3,3.2,2,3.5,2.4c0.5,0.5,0.3,4.3,0.4,16.4c0.1,11.3-0.2,29-0.2,55.3c0,44.7,0.6,61.8,0.1,69.8 + c-0.3,4.4-1.2,6.1-1.9,8.1c-1.2,3.1-3.9,8.2-5.6,10.9l-3.2,5l0.4,40.4c0.4,37.6-0.2,39,1.6,41.7c1.1,1.5,3,6.1,4.6,9.3l3,5.8 + l1.3,7.9l-0.1,19.8l-0.1,15.8l-0.2,97.9c-0.2,93.1,1.2,126.7-0.3,141.1c-0.4,4.2-1.6,6.5-2.3,8.5c-0.9,2.7-2.1,4.9-3.9,7.8 + l-3.7,6.1l0.4,15.6l0.1,25.1c0.1,26.5,0.6,40.4,1.3,40.9c0.6,0.4,2.7,4,4.3,8.5l3.3,9l1.1,9l-0.2,15.9l-0.2,57.9 + c-0.2,61.9,0.4,84.5-0.5,85.5c-0.8,0.9-4.5,2.4-8.4,2.4C340.2,905.8,338,905.1,336.6,903.2L336.6,903.2L336.6,903.2z"/> + <path id="path1154" class="st4" d="M325.2,464.9V72.7h8.5v392.2l1.1,393l-5.3,0.2l-4.9-0.2L325.2,464.9L325.2,464.9z"/> + <path id="path1156" class="st0" d="M345.9,273.3v-38.6h3c1.7,0,3.6,0.9,4.3,2.1c1.7,2.8,1.7,70.3,0,73.1c-0.7,1.2-2.6,2.1-4.3,2.1 + h-3V273.3L345.9,273.3z"/> + <path id="path1158" class="st0" d="M345.9,676.2v-38.7h3c1.8,0,3.6,0.9,4.3,2.1c1.7,2.8,1.7,70.3,0,73.2c-0.7,1.2-2.6,2.1-4.3,2.1 + h-3V676.2L345.9,676.2z"/> + </g> + <g id="g315"> + <g id="g64" class="st10"> + <path id="path36" class="st11" d="M654.6,233.9v79.5h-4.2c-3.3,0-6-2.7-6-6v-67.6c0-3.3,2.7-6,6-6h4.2V233.9z"/> + <path id="path38" class="st11" d="M654.6,636.6v79.5h-4.2c-3.3,0-6-2.7-6-6v-67.6c0-3.3,2.7-6,6-6L654.6,636.6L654.6,636.6z"/> + <path id="path40" class="st11" d="M985.7,134.9l-3.4,3.4C949.1,60.3,872.5,9.6,787.6,9.6h-54.2V7c0-3.3,2.7-6,6-6h51.2 + C875.4,1,952.3,50.8,987,128.2C988,130.5,987.5,133.1,985.7,134.9L985.7,134.9L985.7,134.9z"/> + <path id="path42" class="st11" d="M736.2,94.5V82.8c0-1.6-1.3-3-3-3h-11.7c-1.6,0-3,1.3-3,3l0,0v11.7c0,1.6-1.3,3-3,3h-11.7 + c-1.6,0-3,1.3-3,3l0,0v11.7c0,1.6,1.3,3,3,3h11.7c1.6,0,3,1.3,3,3l0,0v11.7c0,1.6,1.3,3,3,3h11.7c1.6,0,3-1.3,3-3l0,0v-11.7 + c0-1.6,1.3-3,3-3h11.7c1.6,0,3-1.3,3-3l0,0v-11.7c0-1.6-1.3-3-3-3h-11.7C737.5,97.5,736.2,96.1,736.2,94.5L736.2,94.5z"/> + <circle id="circle44" class="st11" cx="825.6" cy="333.9" r="37.5"/> + <circle id="circle46" class="st11" cx="825.6" cy="187.1" r="37.5"/> + <circle id="circle48" class="st11" cx="899" cy="260.5" r="37.5"/> + <circle id="circle50" class="st11" cx="752.2" cy="260.5" r="37.5"/> + <circle id="circle52" class="st11" cx="771.7" cy="721.3" r="27.9"/> + <path id="path54" class="st11" d="M822.3,460.3v12.3c0,3-2.2,5.5-5.2,5.9c-25.2,3.7-45,23.5-48.7,48.7c-0.4,2.9-2.9,5.1-5.9,5.2 + h-12.3C751.9,493.3,783.2,462,822.3,460.3L822.3,460.3L822.3,460.3z"/> + <path id="path56" class="st11" d="M822.3,598.8v12.3c-39.1-1.7-70.3-33-72.1-72h12.3c3,0,5.5,2.2,5.9,5.2 + c3.7,25.2,23.5,45,48.7,48.7C820.1,593.3,822.3,595.8,822.3,598.8L822.3,598.8L822.3,598.8z"/> + <path id="path58" class="st11" d="M901,539c-1.7,39.1-33,70.3-72.1,72v-12.3c0-3,2.2-5.5,5.2-5.9c25.2-3.7,45-23.5,48.7-48.7 + c0.4-2.9,2.9-5.1,5.9-5.2L901,539L901,539z"/> + <path id="path60" class="st11" d="M901,532.4h-12.3c-3,0-5.5-2.2-5.9-5.2c-3.7-25.2-23.5-45-48.7-48.7c-2.9-0.4-5.1-2.9-5.2-5.9 + v-12.3C868,462,899.3,493.3,901,532.4L901,532.4L901,532.4z"/> + <path id="path62" class="st11" d="M901.1,535.7c0,1.1,0,2.2-0.1,3.3h-12.3c-3,0-5.5,2.2-5.9,5.2c-3.7,25.2-23.5,45-48.7,48.7 + c-2.9,0.4-5.1,2.9-5.2,5.9v12.3c-1.1,0.1-2.2,0.1-3.3,0.1s-2.2,0-3.3-0.1v-12.3c0-3-2.2-5.5-5.2-5.9c-25.2-3.7-45-23.5-48.7-48.7 + c-0.4-2.9-2.9-5.1-5.9-5.2h-12.3c-0.1-1.1-0.1-2.2-0.1-3.3s0-2.2,0.1-3.3h12.3c3,0,5.5-2.2,5.9-5.2c3.7-25.2,23.5-45,48.7-48.7 + c2.9-0.4,5.1-2.9,5.2-5.9v-12.3c1.1-0.1,2.2-0.1,3.3-0.1s2.2,0,3.3,0.1v12.3c0,3,2.2,5.5,5.2,5.9c25.2,3.7,45,23.5,48.7,48.7 + c0.4,2.9,2.9,5.1,5.9,5.2H901C901,533.5,901.1,534.6,901.1,535.7L901.1,535.7z"/> + </g> + <path id="path72" d="M658.3,906.6h-7.9c-3.6,0-6.4-2.9-6.5-6.5V742.9c0-4.9,1.2-9.6,3.4-13.9l6.7-13v-79.2l-6.7-13 + c-2.2-4.3-3.4-9.1-3.4-14V340.1c0-4.9,1.2-9.6,3.4-13.9l6.7-13V234l-6.7-13c-2.2-4.3-3.4-9.1-3.4-14V71.2c0-3.6,2.9-6.4,6.5-6.5 + h7.9c3.6,0,6.4,2.9,6.5,6.5c1.1,276.3,1.2,552.6,0,828.9C664.7,903.7,661.8,906.6,658.3,906.6L658.3,906.6L658.3,906.6z + M650.4,65.7c-3,0-5.5,2.4-5.5,5.5V207c0,4.7,1.1,9.3,3.3,13.5l6.8,13.1c0,0.1,0.1,0.1,0.1,0.2v79.5c0,0.1,0,0.2-0.1,0.2 + l-6.8,13.1c-2.2,4.2-3.3,8.8-3.3,13.5v269.7c0,4.7,1.1,9.3,3.3,13.5l6.8,13.1c0,0.1,0.1,0.1,0.1,0.2v79.5c0,0.1,0,0.2-0.1,0.2 + l-6.8,13.1c-2.2,4.2-3.3,8.8-3.3,13.5v157.2c0,3,2.4,5.5,5.5,5.5h7.9c3,0,5.5-2.4,5.5-5.5V71.2c0-3-2.4-5.5-5.5-5.5L650.4,65.7 + L650.4,65.7z"/> + <path id="path74" d="M675.5,858.9h-11.3c-0.3,0-0.5-0.2-0.5-0.5l0,0L664.5,72c0-0.3,0.2-0.5,0.5-0.5h11.3c0.3,0-0.3,0.2-0.3,0.5 + v786.4C676,858.7,675.8,858.9,675.5,858.9z M665.6,857.9h9.4l0.8-785.4h-10.3l1,8.8c-0.6,256.1,4.5,511.8-1.1,768.3 + C665.3,852.3,665.6,855.1,665.6,857.9L665.6,857.9z"/> + <path id="path76" d="M787.4,1000.2H681.2c-3.6,0-6.5-2.9-6.5-6.5v-978c0-3.6,2.9-6.5,6.5-6.5h106.2 + c116.8,0,211.9,95.1,211.9,211.9v567.2C999.3,905.3,904.5,1000.2,787.4,1000.2L787.4,1000.2z M681.2,10.4c-3,0-5.5,2.4-5.5,5.5 + v978c0,3,2.4,5.5,5.5,5.5h106.2c116.3,0,210.9-94.6,210.9-210.9V221.1c0-116.3-94.6-210.9-210.9-210.9L681.2,10.4z"/> + <path id="path128" d="M654.6,314.3h-4.2c-3.8,0-6.9-3.1-7-7v-67.6c0-3.8,3.1-6.9,7-7h4.2c0.6,0,1,0.4,1,1l0,0v79.5 + C655.6,313.9,655.1,314.3,654.6,314.3L654.6,314.3z M650.4,234.8c-2.7,0-5,2.2-5,5v67.6c0,2.7,2.2,5,5,5h3.2v-77.5h-3.2V234.8z"/> + <path id="path130" d="M654.6,717.1h-4.2c-3.8,0-6.9-3.1-7-7v-67.6c0-3.8,3.1-6.9,7-7h4.2c0.6,0,1,0.4,1,1l0,0V716 + C655.6,716.6,655.1,717.1,654.6,717.1L654.6,717.1z M650.4,637.6c-2.7,0-5,2.2-5,5v67.6c0,2.7,2.2,5,5,5h3.2v-77.5L650.4,637.6 + L650.4,637.6z"/> + <path id="path1240" class="st12" d="M805.5,998.7c26.6-2.4,50.5-9.2,75.9-21.8c5.1-2.5,10-5.2,14.9-8 + c19.1-11.3,36.3-27.1,49.6-41.7c25.5-28,45.5-70.1,51.4-116.5c0.1-0.7,0.4-4.3,0.4-6.8c-0.1-6.9,0.7-20,0.8-38.3 + c0.7-77.5,1.1-244,1.1-376.4c0-101.2-0.3-182.5-1-188.9c-2.7-26.1-9.3-49.1-20.8-72.1c-31.8-63.9-93-107.4-164-116.7 + c-11.4-1.5-60.7-1.6-96.6-1.3c-7.7,0.1-14.1-0.1-20.1,0.1c-7.5,0.2-12.9-0.1-16.2,0.2c-1.7,0.2-3,0.8-3.2,1 + c-0.1,0.1-1.4,1.4-1.5,3.3c-0.3,5.2-0.3,17.1-0.4,38c0,4.2-0.1,8.8-0.1,13.8c0,4.9,0.1,10.1,0.1,15.8c-0.1,8.8,0.1,18.6,0.1,29.2 + c0,9.8-0.2,20.5,0,32c0.2,9,0.2,18.4,0.2,28.4c-0.1,15.1,0.6,31.1,0.3,49c-0.2,15,0.1,30,0,46.9c-0.1,11.1,0.1,22.6,0,34.6 + c-0.1,7.2,0,14.5-0.1,22.1c-0.5,52.1,0,112.2,0,180.3c0,254.3-0.3,376.1,0,435.2c0,7.7-0.2,14.5-0.1,20.2c0,2.2,0,4.3,0,6.2 + c0,3.3,0,6.6,0,9.3c0,2.9-0.1,5.8,0,8c0.1,5,0.1,8.1,0.3,10c0.3,2.8,1.3,3.6,1.6,3.8c0.2,0.2,1.3,0.9,3.1,1.1 + c4.5,0.4,14.6,0.3,27.1,0.2c9.9-0.1,21.3-0.2,32.8-0.2C772.7,998.8,805.8,999,805.5,998.7L805.5,998.7L805.5,998.7z"/> + <path id="path78" d="M771.7,763.2c-23.1,0-41.9-18.7-41.9-41.9s18.7-41.9,41.9-41.9c23.2,0,41.9,18.7,41.9,41.9l0,0 + C813.5,744.4,794.8,763.1,771.7,763.2z M771.7,680.4c-22.6,0-40.9,18.3-40.9,40.9s18.3,40.9,40.9,40.9c22.6,0,40.9-18.3,40.9-40.9 + l0,0C812.5,698.7,794.3,680.4,771.7,680.4z"/> + <path id="path88" class="st13" d="M838.9,203.2h-5.5l-7.6-10.9l-8,10.9h-5.4l10.6-16.3l-9.8-15.6h5.2l7.4,10.6l7.3-10.6h5 + l-9.8,15.4L838.9,203.2L838.9,203.2z"/> + <path id="path90" class="st13" d="M765.9,244.5l-11.6,20.6v11.4h-4.4V265l-11.6-20.5h5.3l6.4,11.7l2.1,3l2.4-2.6l6.4-12.1H765.9 + L765.9,244.5z"/> + <path id="path92" class="st13" d="M912.6,276.5h-4.7l-2.2-7l-12.4,0.3l-3.2,6.7h-4.5l9.9-35.2l6.7,3.2L912.6,276.5z M903.9,265.2 + l-4.9-14.6l-4.6,14.8L903.9,265.2L903.9,265.2z"/> + <path id="path132" d="M982.3,139.3h-0.2c-0.3-0.1-0.6-0.3-0.7-0.6C948.4,61,872.1,10.5,787.6,10.6h-54.2c-0.6,0-1-0.4-1-1l0,0V7 + c0-3.8,3.1-6.9,7-7h51.2C875.8,0.1,953,50.1,987.9,127.8c1.2,2.6,0.6,5.7-1.5,7.8L983,139C982.8,139.2,982.5,139.3,982.3,139.3 + L982.3,139.3z M734.4,8.6h53.2c84.7-0.1,161.3,50.2,195,128l2.4-2.4l0,0c1.5-1.4,1.9-3.6,1.1-5.5C951.5,51.7,875,2.1,790.6,2 + h-51.2c-2.7,0-5,2.2-5,5V8.6z"/> + <path id="path134" d="M733.2,133.7h-11.7c-2.2,0-4-1.8-4-4V118c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4v-11.7c0-2.2,1.8-4,4-4 + h11.7c1.1,0,2-0.9,2-2V82.8c0-2.2,1.8-4,4-4h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7 + c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7C737.2,131.9,735.4,133.7,733.2,133.7z M703.9,98.5c-1.1,0-2,0.9-2,2v11.7 + c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2v-11.7c0-2.2,1.8-4,4-4H751c1.1,0,2-0.9,2-2 + v-11.7c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4V82.8c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2v11.7c0,2.2-1.8,4-4,4H703.9z"/> + <path id="path136" d="M825.6,372.4c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 + C864,355.2,846.8,372.4,825.6,372.4z M825.6,297.5c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5c20.2,0,36.5-16.3,36.5-36.5 + C862,313.8,845.7,297.5,825.6,297.5z"/> + <path id="path138" d="M825.6,225.5c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 + C864,208.3,846.8,225.5,825.6,225.5z M825.6,150.6c-20.1,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5c20.2,0,36.5-16.3,36.5-36.5 + C862,166.9,845.7,150.6,825.6,150.6z"/> + <path id="path140" d="M899,299c-21.2,0-38.5-17.2-38.5-38.5S877.7,222,899,222c21.3,0,38.5,17.2,38.5,38.5S920.3,298.9,899,299z + M899,224c-20.1,0-36.5,16.3-36.5,36.5S878.8,297,899,297c20.2,0,36.5-16.3,36.5-36.5S919.2,224,899,224z"/> + <path id="path142" d="M752.2,299c-21.2,0-38.5-17.2-38.5-38.5s17.2-38.5,38.5-38.5c21.3,0,38.5,17.2,38.5,38.5 + C790.6,281.7,773.4,298.9,752.2,299z M752.2,224c-20.1,0-36.5,16.3-36.5,36.5s16.2,36.2,36.4,36.2s36.6-16,36.6-36.2 + C788.6,240.4,772.3,224,752.2,224z"/> + <path id="path144" d="M771.7,750.2c-16,0-28.9-13-28.9-28.9s13-28.9,28.9-28.9s28.9,13,28.9,28.9l0,0 + C800.6,737.3,787.7,750.2,771.7,750.2z M771.7,694.3c-14.9,0-26.9,12.1-26.9,26.9s12.1,26.9,26.9,26.9s26.9-12.1,26.9-26.9 + S786.6,694.4,771.7,694.3z"/> + <path id="path146" d="M762.5,533.4h-12.3c-0.6,0-1-0.4-1-1l0,0c1.7-39.6,33.4-71.3,73-73c0.3,0,0.5,0.1,0.7,0.3 + c0.2,0.2,0.3,0.4,0.3,0.7v12.3c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9C768.9,530.8,766,533.4,762.5,533.4z + M751.3,531.4h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-11.2 + C783.9,463.5,753.4,494,751.3,531.4z"/> + <path id="path148" d="M822.3,612.1C822.3,612.1,822.2,612.1,822.3,612.1c-39.6-1.7-71.3-33.4-73-73c0-0.6,0.4-1,1-1h12.3 + c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9v12.3C823.3,611.6,822.8,612.1,822.3,612.1L822.3,612.1 + L822.3,612.1z M751.3,540c2.2,37.4,32.6,67.8,70,70v-11.2c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6 + c-0.3-2.5-2.4-4.3-4.9-4.3H751.3z"/> + <path id="path150" d="M828.9,612.1c-0.6,0-1-0.4-1-1l0,0v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9 + c0.5-3.4,3.4-6,6.9-6H901c0.6,0,1,0.4,1,1l0,0C900.2,578.7,868.6,610.3,828.9,612.1C829,612.1,829,612.1,828.9,612.1L828.9,612.1 + L828.9,612.1z M888.7,540c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9V610 + c37.4-2.2,67.8-32.6,70-70H888.7z"/> + <path id="path152" d="M901,533.4h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-12.3 + c0-0.3,0.1-0.5,0.3-0.7c0.2-0.2,0.5-0.3,0.7-0.3c39.6,1.7,71.3,33.4,73,73C902,532.9,901.6,533.3,901,533.4L901,533.4L901,533.4z + M829.9,461.4v11.2c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h11.2 + C897.8,494,867.3,463.5,829.9,461.4z"/> + <path id="path154" d="M825.6,612.2c-1.2,0-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 + c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3c-0.5,0-1-0.4-1-0.9c-0.1-1.1-0.1-2.2-0.1-3.4s0-2.3,0.1-3.4 + c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1 + c2.3-0.1,4.5-0.1,6.8,0c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3H901 + c0.5,0,1,0.4,1,0.9c0.1,1.1,0.1,2.2,0.1,3.4s0,2.3-0.1,3.4c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3 + c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v12.3c0,0.5-0.4,1-0.9,1C827.9,612.1,826.8,612.2,825.6,612.2L825.6,612.2 + L825.6,612.2z M823.3,610.1c1.5,0.1,3.1,0.1,4.7,0v-11.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6 + h11.3v-4.6h-11.4c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3c-1.5-0.1-3.1-0.1-4.7,0v11.3 + c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3v4.6h11.3c3.5,0,6.4,2.6,6.9,6 + c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9L823.3,610.1L823.3,610.1L823.3,610.1z"/> + <path id="path1462" class="st4" d="M743,295.5c-6.3-1.7-11.3-3.9-16.5-9.1c-7.7-7.7-11.3-17-10.8-27.7c0.6-13.5,8-25,20-31 + c10.5-5.2,22.6-5.3,32.7-0.3c7.9,3.9,12.8,10,16.6,18c5.2,10.8,4.2,24.4-2.4,34.6c-5,7.6-13.5,13.7-22,15.7 + C755.7,296.8,747.8,296.7,743,295.5L743,295.5L743,295.5z"/> + <path id="path1464" class="st4" d="M819.4,222.9c0-0.3-3.7-0.9-5.7-1.6c-3-1.1-5.9-2.6-8-4c-6.9-4.6-11.7-10.9-14.5-18.9 + c-1.3-3.8-2.1-5.2-2.1-11.4s0.7-7.2,2.1-11c3.9-11.1,11.8-19.1,22.8-23c4.2-1.5,5.2-2.3,11.6-2.3c6.4,0,7.3,0.8,11.6,2.3 + c14.5,5.2,24,17.6,24.7,32.9c0.5,10.6-2.7,19.5-10.2,26.9c-7.6,7.5-14.4,10.7-24.6,11C823.6,223.7,820.2,223,819.4,222.9 + L819.4,222.9L819.4,222.9z"/> + <path id="path1466" class="st4" d="M891.7,296.5c-6.7-1-15.6-5.8-20.8-12.9c-3-4.1-6.2-10.7-7.3-15.7 + c-2.8-13.2,2.6-27.9,13.3-35.9c20-15.1,48.1-6.7,56.4,17c1.9,5.3,3,13.3,1.7,19.2c-1.5,6.8-4.9,12.9-10.2,18.2 + c-5.3,5.2-9.9,8-16.4,9.6C903.5,297.2,896.3,297.2,891.7,296.5L891.7,296.5L891.7,296.5z"/> + <path id="path1468" class="st4" d="M820.4,369.9c-8.1-1.3-14.2-4-20.6-10.3c-3.8-3.7-5.4-6.2-7.2-10c-7.6-15.8-3-33.1,10.8-44.1 + c6.4-5.1,13.6-7.6,22.1-7.6c29.9,0,46.7,33.8,28.7,58c-2.8,3.8-6.3,7.5-10.5,9.8C836.7,369.8,828.1,371.1,820.4,369.9L820.4,369.9 + L820.4,369.9z"/> + <path id="path1500" class="st6" d="M823.6,602.7c-0.3-8.9-0.8-9.7-10.1-11.9c-21.1-4.8-37.2-20.1-42.6-41.3 + c-0.6-2.2-1.1-5.2-1.1-5.9s-1-2.2-2.2-3.5l-2.3-2l-6.9-0.3h-7v-4h5.9c6.6,0,9.9-1,11.2-3.4c0.4-0.8,1.5-3.9,2-7 + c2.7-16.3,14.9-30.7,29.5-38c4.7-2.4,11.4-4.6,15.5-5.4c6.6-1.1,8.2-3.6,8.2-12.8v-5.9h4v6.9c0,6.6,0.1,7,1.8,8.8 + c1.5,1.6,3.9,1.7,9.5,3.1c20.4,5,35.3,20.5,40.7,40.1c0.8,2.9,1.8,6.4,2.2,7.8c1.2,4.3,3.8,5.6,11.7,5.6h6.5v4h-6.6 + c-7,0-9.9,0.8-10.8,3.1c-0.3,0.7-1.3,4.6-2.1,8c-5.4,21.4-21.3,36.7-42.1,41.5c-9.9,2.3-10.7,3.2-10.7,12.8v6.3h-3.8L823.6,602.7 + L823.6,602.7z"/> + <path id="path1504" class="st7" d="M752.2,526.5c1.8-15.3,8.9-31.2,20.4-43c11.9-12.2,27.5-19.2,45.7-21.6l2.8-0.4v6.2 + c0,7.9-0.1,8.9-6.8,10.2c-22.3,4.6-41.8,22.1-46.1,44.2c-1.8,9.1-2.4,9.1-11.1,9.1h-5.4L752.2,526.5L752.2,526.5z"/> + <path id="path1506" class="st7" d="M811.2,608.2c-32.2-6.9-55.7-32.9-59.4-65.6l-0.2-2.1h6.4c6.9,0,6.8,0,7.8,1 + c1.3,1.4,1.5,3.8,2.4,7.9c4.8,21.2,24.2,39.1,46.6,44c6.9,1.5,6.4,2.7,6.4,10.3v5.9l-2.1,0C817.8,609.5,814.3,608.9,811.2,608.2 + L811.2,608.2L811.2,608.2z"/> + <path id="path1508" class="st7" d="M830,603c-0.3-7.3,1-8.2,5.3-9c4.8-0.8,11.1-2.9,16.3-5.5c15.8-7.7,27.1-22,31.6-39.9 + c1-4,1.6-7.2,2.2-7.7c0.7-0.5,3.9-0.5,7.8-0.5h6.2l-0.4,3.8c-3.8,33.2-31.8,61.3-64.8,65l-4,0.5L830,603L830,603z"/> + <path id="path1510" class="st7" d="M885.4,529.8c-1.2-1.1-1.3-3-2.1-6.7c-4.4-20.5-19-36.6-39.3-43.5c-2.4-0.8-5.8-1.7-7.5-2 + c-1.9-0.3-3.9-0.6-4.8-1.5c-1.4-1.4-1.3-2.6-1.3-8.2v-6.2l2.5,0.3c19,2.5,32.7,9.2,45.4,22.1c10.4,10.5,17.4,23.8,20.2,38.1 + c0.6,2.9,1,6.1,1,7.1v1.9h-5.9C888.3,531.3,886.8,531.1,885.4,529.8L885.4,529.8L885.4,529.8z"/> + <path id="path1512" class="st4" d="M720.3,131c-0.5-0.6-0.6-1.7-0.6-7.6v-6.9l-1.1-1.2l-1.1-1.2l-7.1-0.1 + c-4.1-0.1-6.5,0.5-7.6-0.2c-1.3-0.8-0.9-3.1-0.9-7.6s0.1-6.3,0.5-6.8c0.5-0.7,1.1-0.7,7.3-0.8c3.7-0.1,7.1-0.2,7.6-0.3 + c0.5-0.1,1.2-0.7,1.6-1.4c0.7-1.1,0.7-1.9,0.7-7.8c0-3.8,0.2-6.8,0.4-7.3c0.4-0.7,0.9-0.7,7.3-0.7s7,0.1,7.3,0.7 + c0.2,0.4,0.4,3.5,0.4,7.3c0,5.8,0.1,6.7,0.7,7.8c0.4,0.7,1.1,1.3,1.6,1.4s3.9,0.2,7.6,0.3c6.2,0.1,6.8,0.2,7.3,0.8 + c0.4,0.6,0.5,2.2,0.5,6.8s0.7,7.2-0.9,7.9c-1.2,0.5-3.8-0.1-7.6-0.1l-7.1,0.1l-1.1,1.2l-1.1,1.2v6.9c0,6.2-0.1,7-0.7,7.6 + c-0.6,0.5-1.7,0.6-7.1,0.6C721.7,131.7,720.9,131.6,720.3,131L720.3,131L720.3,131z"/> + <path id="path1514" class="st7" d="M766.6,726.4v-5.3h10.5v10.5h-10.5V726.4L766.6,726.4z"/> + <path id="path1518" class="st7" d="M766,761.6c-14.8-2.3-27.2-12.3-32.4-26.2c-7.5-20,2.3-43,22-51.4c6.1-2.6,8-3.5,15.7-3.5 + c6.5,0,7.9,0.6,11.3,1.6c14.4,4.4,24.6,14.9,28.6,29.4c0.8,2.9,1.1,5.2,1.1,10.7c0,5.5-0.3,6.2-1.1,9c-4.8,17.7-20,30-38.1,30.5 + C770.5,761.9,767.4,761.8,766,761.6L766,761.6L766,761.6z M778.1,749.7c11.9-2.6,21.3-13,22.5-25.1c2.2-21.5-18.1-37.7-38.5-30.7 + c-4.2,1.4-7,3.3-10.8,7.1c-2.8,2.8-3.8,4.1-5.3,7.2c-2.6,5.1-3.3,8.7-3,14.6c0.3,6.8,2.2,11.7,6.3,16.9 + C755.9,748,767.4,752,778.1,749.7L778.1,749.7L778.1,749.7z"/> + <path id="path1520" class="st9" d="M648.2,714.3c-1.3-0.8-2.3-0.9-2.5-5.3c-0.2-4.3,0.1-13.5,0.1-32.6c0-32.8-0.5-35.2,0.6-36.3 + c0.1-0.1,0.1-0.2,0.2-0.2c1.1-1.5,2.7-2.1,4.9-2.1h1.9V715h-2C650.1,715,648.9,714.8,648.2,714.3L648.2,714.3L648.2,714.3z"/> + <path id="path1522" class="st9" d="M649.6,312.1c-0.5-0.1-1.3-0.4-1.8-0.8c-0.8-0.5-1.5-0.8-1.8-2.7c-0.5-3.2-0.2-11.4-0.2-35.1 + c0-19.3-0.5-28.4-0.4-32.5c0.1-2.7,0.8-3.2,1-3.6c0.9-1.6,2.4-2.3,4.8-2.3h2.1v77.3l-1.4,0C651.2,312.3,650.1,312.2,649.6,312.1 + L649.6,312.1L649.6,312.1z"/> + <path id="path1524" class="st8" d="M978,126.4c-10.1-20.6-21.9-37.2-38.2-53.9c-34.8-35.7-78.7-57-129.6-63.1 + c-5.1-0.6-13.1-0.8-40.9-1l-34.6-0.2V6.9c0-1,0-3,1.4-3.7l2.1-1.1l30.4,0.2c16.9,0.1,33.6,0.2,37,0.5 + c50.8,3.9,97.1,24.6,133.3,59.5c17.6,16.9,31.5,35.7,42.8,57.9c4.3,8.4,4.9,10.1,4.2,11.8c-0.4,1.1-2.7,4.2-3.2,4.1 + C982.6,136.1,980.5,131.5,978,126.4L978,126.4L978,126.4z"/> + <path id="path1530" class="st4" d="M646.8,903.8c-0.6-0.5-1.7-1.2-1.6-5c0.3-6.7-0.2-27.2-0.1-79.1l0.3-82.6l2.6-6.5l7.9-15 + l0.1-39.4l-0.4-39.6l-7.1-13.2l-2.9-7.7l-0.4-7.3l0.1-48l-0.1-22.1l0.1-34.1l0.1-10.5V483l-0.1-8.6l0-25.6v-19.4l-0.2-27.5 + l0.1-21.6l0-11l0.1-19.6l0-10.2l0.5-5.7l1.9-5.8l3-5.5l5.3-9.8v-39.5l-0.4-39.6l-5.3-9.8l-2.7-5.3l-1.8-5.1l-0.7-7.6l0.2-13.3 + l-0.1-12.2v-11.1l-0.1-10.9l0-9.9V142l0-2.9l0.1-17.4l0-21.5V89.4l-0.1-8.4l0-5.1l-0.2-5.9l2.2-3.1l3.2-1.1l3.2,0.1 + c1.4,0,2.7,0.1,3.9,0.2c0.9,0.1,1.8,0.1,2.6,0.4c1,0.4,1.7,1,2,1.3c0.2,0.2,1.2,1.7,1.3,4.8c0.1,2.1,0,4.7,0,8.3 + c0,4.1,0.1,9.7,0,16.4c-0.1,10,0,23.1-0.1,40.4c0,13.2,0.2,28.7,0.1,47c-0.2,62.8-0.2,158-0.2,301l0,416.2l-1.3,1.8 + C660.4,906.2,649.5,906.5,646.8,903.8L646.8,903.8L646.8,903.8z"/> + <path id="path1532" class="st4" d="M665.8,465.1V72.7h8.6v784.7h-8.6V465.1L665.8,465.1z"/> + <ellipse id="path3879" class="st14" cx="771.6" cy="721.1" rx="40.3" ry="40.3"/> + <ellipse id="path3881" class="st15" cx="771.7" cy="721.4" rx="34.1" ry="34.5"/> + <path id="path96" d="M771.7,694.3l-26.8,26.4h7.7v22.5h38.5v-22.5h7.2L771.7,694.3L771.7,694.3z M779.5,735.2h-15.2v-14.5h15.2 + V735.2z"/> + </g> +</g> +</svg> diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg new file mode 100644 index 00000000..adb6e1e1 --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 1000.4 356.1" style="enable-background:new 0 0 1000.4 356.1;" xml:space="preserve"> +<style type="text/css"> + .st0{opacity:0.1;} + .st1{fill:#FF5F55;} + .st2{fill:#FF5F53;} + .st3{fill:#FFFFFF;} + .st4{fill:#1A1A1A;stroke:#000000;} + .st5{fill:#1A1A1A;stroke:#1A1A1A;} + .st6{fill:#1A1A1A;stroke:#4D4D4D;} + .st7{stroke:#000000;} + .st8{stroke:#4D4D4D;} + .st9{fill:#999595;stroke:#000000;stroke-width:2.39;stroke-linecap:round;stroke-linejoin:round;} + .st10{fill:#3A3D40;stroke:#000000;stroke-width:2.73;stroke-linecap:round;stroke-linejoin:round;} +</style> +<g id="g315"> + <g id="g64" class="st0"> + <path id="path36" class="st1" d="M766.5,11.2H687V7c0-3.3,2.7-6,6-6h67.6c3.3,0,6,2.7,6,6L766.5,11.2L766.5,11.2z"/> + <path id="path38" class="st1" d="M363.8,11.2h-79.5V7c0-3.3,2.7-6,6-6h67.6c3.3,0,6,2.7,6,6L363.8,11.2L363.8,11.2z"/> + <path id="path40" class="st1" d="M865.5,342.3l-3.4-3.4c78-33.2,128.7-109.8,128.7-194.7V90h2.6c3.3,0,6,2.7,6,6v51.2 + c0,84.8-49.8,161.7-127.2,196.4C869.9,344.6,867.3,344.1,865.5,342.3L865.5,342.3L865.5,342.3z"/> + <path id="path42" class="st1" d="M905.9,92.8h11.7c1.6,0,3-1.3,3-3V78.1c0-1.6-1.3-3-3-3l0,0h-11.7c-1.6,0-3-1.3-3-3V60.4 + c0-1.6-1.3-3-3-3l0,0h-11.7c-1.6,0-3,1.3-3,3v11.7c0,1.6-1.3,3-3,3l0,0h-11.7c-1.6,0-3,1.3-3,3v11.7c0,1.6,1.3,3,3,3l0,0h11.7 + c1.6,0,3,1.3,3,3v11.7c0,1.6,1.3,3,3,3l0,0h11.7c1.6,0,3-1.3,3-3V95.8C902.9,94.1,904.3,92.8,905.9,92.8L905.9,92.8z"/> + <circle id="circle44" class="st1" cx="666.5" cy="182.2" r="37.5"/> + <circle id="circle46" class="st1" cx="813.3" cy="182.2" r="37.5"/> + <circle id="circle48" class="st1" cx="739.9" cy="255.6" r="37.5"/> + <circle id="circle50" class="st1" cx="739.9" cy="108.8" r="37.5"/> + <circle id="circle52" class="st1" cx="279.1" cy="128.3" r="27.9"/> + <path id="path54" class="st1" d="M540.1,178.9h-12.3c-3,0-5.5-2.2-5.9-5.2c-3.7-25.2-23.5-45-48.7-48.7c-2.9-0.4-5.1-2.9-5.2-5.9 + v-12.3C507.1,108.5,538.4,139.8,540.1,178.9L540.1,178.9L540.1,178.9z"/> + <path id="path56" class="st1" d="M401.6,178.9h-12.3c1.7-39.1,33-70.3,72-72.1v12.3c0,3-2.2,5.5-5.2,5.9 + c-25.2,3.7-45,23.5-48.7,48.7C407.1,176.7,404.6,178.9,401.6,178.9L401.6,178.9L401.6,178.9z"/> + <path id="path58" class="st1" d="M461.4,257.6c-39.1-1.7-70.3-33-72-72.1h12.3c3,0,5.5,2.2,5.9,5.2c3.7,25.2,23.5,45,48.7,48.7 + c2.9,0.4,5.1,2.9,5.2,5.9L461.4,257.6L461.4,257.6z"/> + <path id="path60" class="st1" d="M468,257.6v-12.3c0-3,2.2-5.5,5.2-5.9c25.2-3.7,45-23.5,48.7-48.7c0.4-2.9,2.9-5.1,5.9-5.2h12.3 + C538.4,224.6,507.1,255.9,468,257.6L468,257.6L468,257.6z"/> + <path id="path62" class="st1" d="M464.7,257.7c-1.1,0-2.2,0-3.3-0.1v-12.3c0-3-2.2-5.5-5.2-5.9c-25.2-3.7-45-23.5-48.7-48.7 + c-0.4-2.9-2.9-5.1-5.9-5.2h-12.3c-0.1-1.1-0.1-2.2-0.1-3.3s0-2.2,0.1-3.3h12.3c3,0,5.5-2.2,5.9-5.2c3.7-25.2,23.5-45,48.7-48.7 + c2.9-0.4,5.1-2.9,5.2-5.9v-12.3c1.1-0.1,2.2-0.1,3.3-0.1s2.2,0,3.3,0.1v12.3c0,3,2.2,5.5,5.2,5.9c25.2,3.7,45,23.5,48.7,48.7 + c0.4,2.9,2.9,5.1,5.9,5.2h12.3c0.1,1.1,0.1,2.2,0.1,3.3s0,2.2-0.1,3.3h-12.3c-3,0-5.5,2.2-5.9,5.2c-3.7,25.2-23.5,45-48.7,48.7 + c-2.9,0.4-5.1,2.9-5.2,5.9v12.3C466.9,257.6,465.8,257.7,464.7,257.7L464.7,257.7z"/> + </g> + <path id="path72" d="M93.8,14.9V7c0-3.6,2.9-6.4,6.5-6.5h157.2c4.9,0,9.6,1.2,13.9,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,14-3.4 + h269.7c4.9,0,9.6,1.2,13.9,3.4l13,6.7h79.2l13-6.7c4.3-2.2,9.1-3.4,14-3.4h135.8c3.6,0,6.4,2.9,6.5,6.5v7.9c0,3.6-2.9,6.4-6.5,6.5 + c-276.3,1.1-552.6,1.2-828.9,0C96.7,21.3,93.8,18.4,93.8,14.9L93.8,14.9L93.8,14.9z M934.7,7c0-3-2.4-5.5-5.5-5.5H793.4 + c-4.7,0-9.3,1.1-13.5,3.3l-13.1,6.8c-0.1,0-0.1,0.1-0.2,0.1h-79.5c-0.1,0-0.2,0-0.2-0.1l-13.1-6.8c-4.2-2.2-8.8-3.3-13.5-3.3H390.6 + c-4.7,0-9.3,1.1-13.5,3.3L364,11.6c-0.1,0-0.1,0.1-0.2,0.1h-79.5c-0.1,0-0.2,0-0.2-0.1L271,4.8c-4.2-2.2-8.8-3.3-13.5-3.3H100.3 + c-3,0-5.5,2.4-5.5,5.5v7.9c0,3,2.4,5.5,5.5,5.5h828.9c3,0,5.5-2.4,5.5-5.5L934.7,7L934.7,7z"/> + <path id="path74" d="M141.5,32.1V20.8c0-0.3,0.2-0.5,0.5-0.5l0,0l786.4,0.8c0.3,0,0.5,0.2,0.5,0.5v11.3c0,0.3-0.2-0.3-0.5-0.3H142 + C141.7,32.6,141.5,32.4,141.5,32.1z M142.5,22.2v9.4L928,32.4V22.1l-8.8,1c-256.1-0.6-511.8,4.5-768.3-1.1 + C148.1,21.9,145.3,22.2,142.5,22.2L142.5,22.2z"/> + <path id="path76" d="M0.2,144V37.8c0-3.6,2.9-6.5,6.5-6.5h978c3.6,0,6.5,2.9,6.5,6.5V144c0,116.8-95.1,211.9-211.9,211.9H212.1 + C95.1,355.9,0.2,261.1,0.2,144L0.2,144L0.2,144z M990.1,37.8c0-3-2.4-5.5-5.5-5.5H6.6c-3,0-5.5,2.4-5.5,5.5V144 + c0,116.3,94.6,210.9,210.9,210.9h567.3c116.3,0,210.9-94.6,210.9-210.9L990.1,37.8L990.1,37.8z"/> + <path id="path128" d="M686.1,11.2V7c0-3.8,3.1-6.9,7-7h67.6c3.8,0,6.9,3.1,7,7v4.2c0,0.6-0.4,1-1,1l0,0h-79.5 + C686.5,12.2,686.1,11.7,686.1,11.2L686.1,11.2z M765.6,7c0-2.7-2.2-5-5-5H693c-2.7,0-5,2.2-5,5v3.2h77.5L765.6,7L765.6,7z"/> + <path id="path130" d="M283.3,11.2V7c0-3.8,3.1-6.9,7-7h67.6c3.8,0,6.9,3.1,7,7v4.2c0,0.6-0.4,1-1,1l0,0h-79.5 + C283.8,12.2,283.3,11.7,283.3,11.2L283.3,11.2z M362.8,7c0-2.7-2.2-5-5-5h-67.6c-2.7,0-5,2.2-5,5v3.2h77.5L362.8,7L362.8,7z"/> + <path id="path1240" class="st2" d="M1.8,162.1c2.4,26.6,9.2,50.5,21.8,76c2.5,5.1,5.2,10,8,14.9c11.3,19.1,27.1,36.3,41.7,49.6 + c28,25.5,70.1,45.5,116.5,51.4c0.7,0.1,4.3,0.4,6.8,0.4c6.9-0.1,20,0.7,38.3,0.8c77.5,0.7,244,1.1,376.4,1.1 + c101.2,0,182.5-0.3,188.9-1c26.1-2.7,49.1-9.3,72.1-20.8c63.9-31.8,107.4-93,116.7-164c1.5-11.4,1.6-60.7,1.3-96.6 + c-0.1-7.7,0.1-14.1-0.1-20.1c-0.2-7.5,0.1-12.9-0.2-16.2c-0.2-1.7-0.8-3-1-3.2c-0.1-0.1-1.4-1.4-3.3-1.5c-5.2-0.3-17.1-0.3-38-0.4 + c-4.2,0-8.8-0.1-13.8-0.1c-4.9,0-10.1,0.1-15.8,0.1c-8.8,0-18.6,0.1-29.2,0.1c-9.8,0-20.5-0.2-32,0c-9,0.2-18.4,0.2-28.4,0.2 + c-15.1-0.1-31.1,0.6-49,0.3c-15-0.2-30,0.1-46.9,0c-11.1-0.1-22.6,0.1-34.6,0c-7.2-0.1-14.5,0-22.1-0.1c-52.1-0.5-112.2,0-180.3,0 + c-254.3,0-376.1-0.3-435.2,0c-7.7,0-14.5-0.2-20.2-0.1c-2.2,0-4.3,0-6.2,0c-3.3,0-6.6,0-9.3,0c-2.9,0-5.8-0.1-8,0 + c-5,0.1-8.1,0.1-10,0.3c-2.8,0.3-3.6,1.3-3.8,1.6c-0.2,0.2-0.9,1.3-1.1,3.1c-0.4,4.5-0.3,14.6-0.2,27.1c0.1,9.9,0.2,21.3,0.2,32.8 + C1.7,129.3,1.5,162.4,1.8,162.1L1.8,162.1L1.8,162.1z"/> + <path id="path78" d="M237.2,128.3c0-23.1,18.7-41.9,41.9-41.9s41.9,18.7,41.9,41.9s-18.7,41.9-41.9,41.9l0,0 + C256,170.1,237.3,151.4,237.2,128.3z M320,128.3c0-22.6-18.3-40.9-40.9-40.9s-40.9,18.3-40.9,40.9s18.3,40.9,40.9,40.9l0,0 + C301.7,169.1,320,150.9,320,128.3z"/> + <path id="path92" class="st3" d="M723.9,269.2v-4.7l7-2.2l-0.3-12.4l-6.7-3.2v-4.5l35.2,9.9l-3.2,6.7L723.9,269.2z M735.2,260.5 + l14.6-4.9L735,251L735.2,260.5L735.2,260.5z"/> + <path id="path132" d="M861.1,338.9v-0.2c0.1-0.3,0.3-0.6,0.6-0.7c77.7-33,128.2-109.3,128.1-193.8V90c0-0.6,0.4-1,1-1l0,0h2.6 + c3.8,0,6.9,3.1,7,7v51.2c-0.1,85.2-50.1,162.4-127.8,197.3c-2.6,1.2-5.7,0.6-7.8-1.5l-3.4-3.4C861.2,339.4,861.1,339.1,861.1,338.9 + L861.1,338.9z M991.8,91v53.2c0.1,84.7-50.2,161.3-128,195l2.4,2.4l0,0c1.4,1.5,3.6,1.9,5.5,1.1c77-34.6,126.6-111.1,126.7-195.5 + V96c0-2.7-2.2-5-5-5H991.8z"/> + <path id="path134" d="M866.7,89.8V78.1c0-2.2,1.8-4,4-4h11.7c1.1,0,2-0.9,2-2V60.4c0-2.2,1.8-4,4-4h11.7c2.2,0,4,1.8,4,4v11.7 + c0,1.1,0.9,2,2,2h11.5c2.2,0,4,1.8,4,4v11.7c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7c0,2.2-1.8,4-4,4h-11.7c-2.2,0-4-1.8-4-4 + V95.8c0-1.1-0.9-2-2-2h-11.7C868.5,93.8,866.7,92,866.7,89.8z M901.9,60.5c0-1.1-0.9-2-2-2h-11.7c-1.1,0-2,0.9-2,2v11.7 + c0,2.2-1.8,4-4,4h-11.7c-1.1,0-2,0.9-2,2v11.7c0,1.1,0.9,2,2,2h11.7c2.2,0,4,1.8,4,4v11.7c0,1.1,0.9,2,2,2h11.7c1.1,0,2-0.9,2-2 + V95.9c0-2.2,1.8-4,4-4h11.7c1.1,0,2-0.9,2-2V78.2c0-1.1-0.9-2-2-2h-11.7c-2.2,0-4-1.8-4-4V60.5z"/> + <path id="path136" d="M628,182.2c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 + C645.2,220.6,628,203.4,628,182.2z M702.9,182.2c0-20.1-16.3-36.5-36.5-36.5s-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5 + C686.6,218.6,702.9,202.3,702.9,182.2z"/> + <path id="path138" d="M774.9,182.2c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 + C792.1,220.6,774.9,203.4,774.9,182.2z M849.8,182.2c0-20.1-16.3-36.5-36.5-36.5c-20.2,0-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5 + C833.5,218.6,849.8,202.3,849.8,182.2z"/> + <path id="path140" d="M701.4,255.6c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5S701.5,276.9,701.4,255.6z + M776.4,255.6c0-20.1-16.3-36.5-36.5-36.5s-36.5,16.3-36.5,36.5s16.3,36.5,36.5,36.5S776.4,275.8,776.4,255.6z"/> + <path id="path142" d="M701.4,108.8c0-21.2,17.2-38.5,38.5-38.5s38.5,17.2,38.5,38.5s-17.2,38.5-38.5,38.5 + C718.7,147.2,701.5,130,701.4,108.8z M776.4,108.8c0-20.1-16.3-36.5-36.5-36.5s-36.2,16.2-36.2,36.4s16,36.6,36.2,36.6 + C760,145.2,776.4,128.9,776.4,108.8z"/> + <path id="path144" d="M250.2,128.3c0-16,13-28.9,28.9-28.9s28.9,13,28.9,28.9s-13,28.9-28.9,28.9l0,0 + C263.1,157.2,250.2,144.3,250.2,128.3z M306.1,128.3c0-14.9-12.1-26.9-26.9-26.9s-26.9,12.1-26.9,26.9s12.1,26.9,26.9,26.9 + S306,143.2,306.1,128.3z"/> + <path id="path146" d="M467,119.1v-12.3c0-0.6,0.4-1,1-1l0,0c39.6,1.7,71.3,33.4,73,73c0,0.3-0.1,0.5-0.3,0.7s-0.4,0.3-0.7,0.3 + h-12.3c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9C469.6,125.5,467,122.6,467,119.1z M469,107.9v11.2 + c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3H539C536.9,140.5,506.4,110,469,107.9z"/> + <path id="path148" d="M388.3,178.9C388.3,178.9,388.3,178.8,388.3,178.9c1.7-39.6,33.4-71.3,73-73c0.6,0,1,0.4,1,1v12.3 + c0,3.5-2.6,6.4-6,6.9c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-12.3C388.8,179.9,388.3,179.4,388.3,178.9L388.3,178.9 + L388.3,178.9z M460.4,107.9c-37.4,2.2-67.8,32.6-70,70h11.2c2.5,0,4.6-1.8,4.9-4.3c3.9-25.6,24-45.7,49.6-49.6 + c2.5-0.3,4.3-2.4,4.3-4.9V107.9z"/> + <path id="path150" d="M388.3,185.5c0-0.6,0.4-1,1-1l0,0h12.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9 + c3.4,0.5,6,3.4,6,6.9v12.3c0,0.6-0.4,1-1,1l0,0C421.7,256.8,390.1,225.2,388.3,185.5C388.3,185.6,388.3,185.6,388.3,185.5 + L388.3,185.5L388.3,185.5z M460.4,245.3c0-2.5-1.8-4.6-4.3-4.9c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-11.2 + c2.2,37.4,32.6,67.8,70,70V245.3z"/> + <path id="path152" d="M467,257.6v-12.3c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h12.3 + c0.3,0,0.5,0.1,0.7,0.3s0.3,0.5,0.3,0.7c-1.7,39.6-33.4,71.3-73,73C467.5,258.6,467.1,258.2,467,257.6L467,257.6L467,257.6z + M539,186.5h-11.2c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9v11.2 + C506.4,254.4,536.9,223.9,539,186.5z"/> + <path id="path154" d="M388.2,182.2c0-1.2,0-2.3,0.1-3.4c0-0.5,0.5-0.9,1-0.9h12.3c2.5,0,4.6-1.8,4.9-4.3 + c3.9-25.6,24-45.7,49.6-49.6c2.5-0.3,4.3-2.4,4.3-4.9v-12.3c0-0.5,0.4-1,0.9-1c1.1-0.1,2.2-0.1,3.4-0.1s2.3,0,3.4,0.1 + c0.5,0,0.9,0.5,0.9,1v12.3c0,2.5,1.8,4.6,4.3,4.9c25.6,3.9,45.7,24,49.6,49.6c0.3,2.5,2.4,4.3,4.9,4.3h12.3c0.5,0,1,0.4,1,0.9 + c0.1,2.3,0.1,4.5,0,6.8c0,0.5-0.5,0.9-1,0.9h-12.3c-2.5,0-4.6,1.8-4.9,4.3c-3.9,25.6-24,45.7-49.6,49.6c-2.5,0.3-4.3,2.4-4.3,4.9 + v12.3c0,0.5-0.4,1-0.9,1c-1.1,0.1-2.2,0.1-3.4,0.1s-2.3,0-3.4-0.1c-0.5,0-0.9-0.5-0.9-1v-12.3c0-2.5-1.8-4.6-4.3-4.9 + c-25.6-3.9-45.7-24-49.6-49.6c-0.3-2.5-2.4-4.3-4.9-4.3h-12.3c-0.5,0-1-0.4-1-0.9C388.3,184.5,388.2,183.4,388.2,182.2L388.2,182.2 + L388.2,182.2z M390.3,179.9c-0.1,1.5-0.1,3.1,0,4.7h11.3c3.5,0,6.4,2.6,6.9,6c3.7,24.7,23.1,44.2,47.9,47.9c3.4,0.5,6,3.4,6,6.9 + v11.3h4.6v-11.4c0-3.5,2.6-6.4,6-6.9c24.7-3.7,44.2-23.1,47.9-47.9c0.5-3.4,3.4-6,6.9-6h11.3c0.1-1.5,0.1-3.1,0-4.7h-11.3 + c-3.5,0-6.4-2.6-6.9-6c-3.7-24.7-23.1-44.2-47.9-47.9c-3.4-0.5-6-3.4-6-6.9v-11.3h-4.6V119c0,3.5-2.6,6.4-6,6.9 + c-24.7,3.7-44.2,23.1-47.9,47.9c-0.5,3.4-3.4,6-6.9,6h-11.3V179.9L390.3,179.9z"/> + <path id="path1462" class="st4" d="M705,99.6c1.7-6.3,3.9-11.3,9.1-16.5c7.7-7.7,17-11.3,27.7-10.8c13.5,0.6,25,8,31,20 + c5.2,10.5,5.3,22.5,0.3,32.7c-4,7.9-10,12.8-18,16.6c-10.8,5.2-24.4,4.2-34.6-2.4c-7.7-5-13.7-13.5-15.7-22 + C703.6,112.3,703.7,104.4,705,99.6L705,99.6L705,99.6z"/> + <path id="path1464" class="st4" d="M777.6,176c0.3,0,0.9-3.7,1.6-5.7c1.1-3,2.6-5.9,4-8c4.6-6.9,10.9-11.7,18.9-14.5 + c3.8-1.3,5.2-2.1,11.5-2.1s7.2,0.7,11,2.1c11.1,3.9,19.1,11.8,23,22.8c1.5,4.2,2.3,5.2,2.3,11.6s-0.8,7.3-2.3,11.6 + c-5.2,14.5-17.6,24-32.9,24.7c-10.6,0.5-19.5-2.7-26.9-10.2c-7.5-7.6-10.7-14.4-11-24.6C776.7,180.2,777.4,176.7,777.6,176 + L777.6,176L777.6,176z"/> + <path id="path1466" class="st4" d="M704,248.2c1-6.7,5.8-15.6,12.9-20.8c4.1-3,10.7-6.2,15.7-7.3c13.2-2.8,27.9,2.6,35.9,13.3 + c15.2,20,6.7,48.1-17,56.4c-5.3,1.9-13.3,3-19.2,1.7c-6.8-1.5-12.9-4.9-18.2-10.2c-5.2-5.3-8-9.9-9.6-16.4 + C703.3,260.1,703.3,252.9,704,248.2L704,248.2L704,248.2z"/> + <path id="path1468" class="st4" d="M630.6,177c1.3-8.1,4-14.2,10.3-20.6c3.7-3.8,6.2-5.4,10-7.2c15.8-7.6,33.1-3,44.1,10.8 + c5.1,6.4,7.6,13.6,7.6,22.1c0,29.9-33.8,46.7-58,28.7c-3.8-2.8-7.5-6.3-9.8-10.5C630.7,193.3,629.4,184.7,630.6,177L630.6,177 + L630.6,177z"/> + <path id="path1500" class="st5" d="M397.8,180.2c8.9-0.3,9.7-0.8,11.9-10.1c4.8-21.1,20.1-37.2,41.3-42.6c2.2-0.5,5.2-1.1,5.9-1.1 + s2.2-1,3.5-2.2l2-2.3l0.3-6.9v-7h4v5.9c0,6.6,1,9.9,3.4,11.2c0.8,0.4,3.9,1.5,7,2c16.3,2.7,30.7,14.9,38,29.5 + c2.4,4.7,4.6,11.4,5.4,15.5c1.1,6.6,3.6,8.2,12.8,8.2h5.9v4H532c-6.6,0-7,0.1-8.8,1.8c-1.6,1.5-1.7,3.9-3.1,9.5 + c-5,20.4-20.5,35.3-40.2,40.7c-2.9,0.8-6.4,1.8-7.8,2.2c-4.3,1.2-5.6,3.8-5.6,11.7v6.5h-4V250c0-7-0.8-9.9-3.1-10.8 + c-0.7-0.3-4.6-1.3-8-2.1c-21.4-5.4-36.7-21.3-41.5-42.1c-2.3-10-3.2-10.7-12.8-10.7h-6.3v-3.8L397.8,180.2L397.8,180.2z"/> + <path id="path1504" class="st6" d="M473.9,108.8c15.3,1.8,31.2,8.9,43,20.4c12.2,12,19.2,27.5,21.6,45.7l0.4,2.8h-6.2 + c-7.9,0-8.9-0.1-10.2-6.8c-4.6-22.3-22.1-41.8-44.2-46.1c-9.1-1.8-9.1-2.4-9.1-11.1v-5.4L473.9,108.8L473.9,108.8z"/> + <path id="path1506" class="st6" d="M392.2,167.8c6.9-32.2,32.9-55.7,65.6-59.4l2.1-0.2v6.4c0,6.9,0,6.8-1,7.8 + c-1.4,1.3-3.8,1.5-7.9,2.4c-21.2,4.8-39.1,24.2-44,46.6c-1.5,6.9-2.7,6.4-10.3,6.4h-5.9l0-2.1C391,174.4,391.5,170.9,392.2,167.8 + L392.2,167.8L392.2,167.8z"/> + <path id="path1508" class="st6" d="M397.4,186.6c7.3-0.3,8.2,1,9,5.3c0.8,4.8,2.9,11.1,5.5,16.3c7.7,15.8,22,27.1,39.9,31.6 + c4,1,7.2,1.6,7.7,2.2c0.5,0.7,0.5,3.9,0.5,7.8v6.2l-3.8-0.4c-33.2-3.8-61.3-31.8-65-64.8l-0.5-4L397.4,186.6L397.4,186.6z"/> + <path id="path1510" class="st6" d="M470.6,242c1.1-1.2,3-1.3,6.7-2.1c20.5-4.4,36.6-19,43.5-39.3c0.8-2.4,1.7-5.8,2-7.5 + c0.3-1.9,0.6-3.9,1.5-4.8c1.4-1.4,2.6-1.3,8.2-1.3h6.2l-0.3,2.5c-2.5,19-9.2,32.7-22.1,45.4c-10.6,10.4-23.8,17.4-38.2,20.2 + c-2.9,0.5-6.1,1-7.1,1h-1.9v-5.9C469.2,244.9,469.3,243.4,470.6,242L470.6,242L470.6,242z"/> + <path id="path1512" class="st4" d="M869.4,76.9c0.6-0.5,1.7-0.6,7.5-0.6h6.9l1.2-1.1l1.2-1.1l0.1-7.1c0.1-4.1-0.5-6.5,0.2-7.6 + c0.8-1.3,3.1-0.9,7.6-0.9s6.3,0.1,6.8,0.5c0.7,0.5,0.7,1.1,0.8,7.3c0,3.7,0.2,7.1,0.3,7.6c0.1,0.5,0.7,1.2,1.4,1.6 + c1.1,0.7,1.9,0.7,7.8,0.7c3.8,0,6.9,0.2,7.3,0.4c0.7,0.4,0.7,0.9,0.7,7.3s0,7-0.7,7.3c-0.4,0.2-3.5,0.4-7.3,0.4 + c-5.8,0-6.7,0.1-7.8,0.7c-0.7,0.4-1.3,1.1-1.4,1.6s-0.2,3.9-0.3,7.6c-0.1,6.2-0.2,6.8-0.8,7.3c-0.6,0.4-2.2,0.5-6.8,0.5 + c-4.7,0-7.2,0.7-7.9-0.9c-0.5-1.2,0.1-3.8,0.1-7.5l-0.1-7.1l-1.2-1.1l-1.2-1.1h-6.9c-6.2,0-7-0.1-7.5-0.7c-0.5-0.6-0.6-1.7-0.6-7.1 + C868.7,78.2,868.8,77.5,869.4,76.9L869.4,76.9L869.4,76.9z"/> + <path id="path1514" class="st6" d="M274,123.2h5.3v10.5h-10.5v-10.5H274L274,123.2z"/> + <path id="path1518" class="st6" d="M238.8,122.6c2.3-14.8,12.3-27.2,26.2-32.4c20-7.5,43,2.3,51.4,22c2.6,6.1,3.5,8,3.5,15.7 + c0,6.5-0.6,7.9-1.6,11.3c-4.4,14.4-14.9,24.6-29.4,28.6c-2.9,0.8-5.2,1.1-10.7,1.1c-5.5,0-6.2-0.3-9-1.1 + c-17.7-4.8-29.9-20-30.5-38.1C238.5,127.1,238.6,124,238.8,122.6L238.8,122.6L238.8,122.6z M250.8,134.7 + c2.6,11.9,13,21.3,25.1,22.5c21.5,2.2,37.7-18.1,30.7-38.5c-1.4-4.2-3.3-7-7.1-10.8c-2.8-2.8-4.1-3.8-7.2-5.3 + c-5.1-2.6-8.7-3.3-14.6-3c-6.8,0.3-11.7,2.2-16.9,6.3C252.4,112.5,248.4,124,250.8,134.7L250.8,134.7L250.8,134.7z"/> + <path id="path1520" class="st7" d="M286.1,4.8c0.8-1.3,0.9-2.3,5.3-2.5c4.3-0.2,13.5,0.1,32.6,0.1c32.8,0,35.2-0.5,36.3,0.6 + c0.1,0.1,0.2,0.1,0.2,0.2c1.5,1.1,2.1,2.7,2.1,4.9v1.9h-77.3v-2C285.4,6.6,285.7,5.5,286.1,4.8L286.1,4.8L286.1,4.8z"/> + <path id="path1522" class="st7" d="M688.3,6.2c0.1-0.5,0.4-1.3,0.8-1.8c0.5-0.8,0.8-1.5,2.7-1.8c3.2-0.5,11.4-0.2,35.1-0.2 + c19.3,0,28.3-0.5,32.5-0.4c2.7,0.1,3.2,0.8,3.6,1c1.6,0.9,2.3,2.4,2.3,4.8v2.1h-77.3l0-1.4C688.1,7.7,688.2,6.7,688.3,6.2 + L688.3,6.2L688.3,6.2z"/> + <path id="path1524" class="st8" d="M874,334.6c20.6-10.1,37.2-21.9,53.9-38.2c35.7-34.8,57-78.7,63.1-129.7 + c0.6-5.1,0.8-13.1,1-40.9l0.2-34.6h1.4c1,0,3,0,3.7,1.4l1.1,2.1l-0.2,30.4c-0.1,16.9-0.2,33.5-0.5,37 + c-3.9,50.8-24.6,97.1-59.5,133.3c-16.9,17.6-35.7,31.5-57.9,42.8c-8.4,4.3-10.1,4.9-11.8,4.2c-1.1-0.4-4.2-2.7-4.1-3.2 + C864.4,339.2,869,337.1,874,334.6L874,334.6L874,334.6z"/> + <path id="path1530" class="st4" d="M96.7,3.4c0.5-0.6,1.2-1.7,5-1.6c6.7,0.3,27.2-0.2,79.1-0.1L263.4,2l6.5,2.6l15,7.9l39.4,0.1 + l39.6-0.4l13.2-7.1l7.7-2.9l7.3-0.4l48,0.1l22.1-0.1l34.1,0.1l10.5,0.1h10.5l8.6-0.1l25.6,0h19.4l27.5-0.2L620,1.8l11,0l19.6,0.1 + l10.2,0l5.7,0.5l5.8,1.9l5.5,3l9.8,5.3h39.5l39.6-0.4l9.8-5.3l5.3-2.7l5.1-1.8l7.6-0.7l13.3,0.2l12.2-0.1h11.1l10.9,0l9.9,0h6.5 + l2.9,0l17.4,0.1l21.5,0h10.8l8.4-0.1l5.1,0l5.9-0.2l3.1,2.2l1.1,3.2l-0.1,3.2c0,1.4-0.1,2.7-0.2,3.9c-0.1,0.9-0.1,1.8-0.4,2.6 + c-0.4,1-1,1.7-1.3,2c-0.2,0.2-1.7,1.2-4.8,1.3c-2.1,0.1-4.7,0-8.3,0c-4.1,0-9.7,0.1-16.4,0c-10-0.1-23.1,0-40.4-0.1 + c-13.2,0-28.7,0.2-47,0.1c-62.8-0.2-158-0.2-301-0.2L98.7,20l-1.8-1.3C94.3,16.9,94,6.1,96.7,3.4L96.7,3.4L96.7,3.4z"/> + <path id="path1532" class="st4" d="M535.4,22.4h392.4V31H143v-8.6H535.4L535.4,22.4z"/> + <ellipse id="path3879" class="st9" cx="279.4" cy="128.2" rx="40.3" ry="40.3"/> + <ellipse id="path3881" class="st10" cx="279.1" cy="128.2" rx="34.5" ry="34.1"/> + <path id="path96" d="M306.1,128.3l-26.4-26.8v7.7h-22.5v38.5h22.5v7.2L306.1,128.3L306.1,128.3z M265.2,136.1v-15.2h14.5v15.2 + H265.2z"/> +</g> +</svg> diff --git a/src/Ryujinx/Assets/Icons/Controller_ProCon.svg b/src/Ryujinx/Assets/Icons/Controller_ProCon.svg new file mode 100644 index 00000000..53eef82c --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_ProCon.svg @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 996.25 690.92"> + <defs> + <style> + .cls-1 { + stroke: #fff; + stroke-miterlimit: 10; + stroke-width: 2px; + } + + .cls-1, .cls-2 { + fill: #444542; + } + + .cls-3 { + fill: #3b3b3b; + } + + .cls-3, .cls-4, .cls-2, .cls-5, .cls-6, .cls-7 { + stroke-width: 0px; + } + + .cls-4 { + fill: #3b3c3a; + } + + .cls-5 { + fill: #454644; + } + + .cls-6 { + fill: #20221f; + } + + .cls-7 { + fill: #121212; + } + </style> + </defs> + <g id="Front"> + <path id="Right_Grip" data-name="Right Grip" class="cls-6" d="m739.17,492.09c34,28.2,27.6,35.9,68.5,108.5,36.7,74.7,64.4,104.4,125.1,84.1h0c95.3-57.9,59.3-145.3,43.6-275.2-10-60.6-35.6-190.3-35.6-190.3l-201.6,272.9Z"/> + <path id="Left_Grip" data-name="Left Grip" class="cls-6" d="m55.47,219.19s-25.6,129.7-35.6,190.3c-15.7,129.9-51.7,217.2,43.6,275.1h0c60.8,20.3,88.4-9.4,125.1-84.1,40.9-72.7,34.5-80.3,68.5-108.5L55.47,219.19Z"/> + <path id="Right_Bumper" data-name="Right Bumper" class="cls-3" d="m649.47,19.99c10.1-4.3,39.7-22.5,58.7-19.7,59.5.9,166.7,17.7,172.6,81.2"/> + <path id="Left_Bumper" data-name="Left Bumper" class="cls-3" d="m115.57,81.49C121.47,18.09,228.57,1.29,288.17.29c19-2.8,48.6,15.4,58.7,19.7"/> + <path id="Background" class="cls-7" d="m739.17,492.09c35.5-30.8,68.5-74.7,96-113.5,26.9-36.3,94.7-136.7,105.6-159.3,0-2.4-6.3-30.1-12.8-56.2C892.27,3.49,675.37,19.69,498.17,19.69S104.07,3.49,68.27,162.99c-6.5,26-12.8,53.8-12.8,56.2,10.9,22.6,78.8,123,105.6,159.3,27.5,38.8,60.5,82.8,96,113.5"/> + <g id="Directional_Pad" data-name="Directional Pad"> + <path id="Background-2" data-name="Background" class="cls-2" d="m439.37,325.09h-40c-2.8,0-5-2.2-5-5v-40c0-2.8-2.2-5-5-5h-30c-2.8,0-5,2.2-5,5v40c0,2.8-2.2,5-5,5h-40c-2.8,0-5,2.2-5,5v30c0,2.8,2.2,5,5,5h40c2.8,0,5,2.2,5,5v40c0,2.8,2.2,5,5,5h30c2.8,0,5-2.2,5-5v-40c0-2.8,2.2-5,5-5h40c2.8,0,5-2.2,5-5v-30c0-2.7-2.2-5-5-5Z"/> + </g> + <g id="R_Thumbstick" data-name="R Thumbstick"> + <circle id="Background-3" data-name="Background" class="cls-6" cx="623.77" cy="345.09" r="55"/> + <circle id="Stick" class="cls-1" cx="623.77" cy="345.09" r="45"/> + </g> + <g id="L_Thumbstick" data-name="L Thumbstick"> + <circle id="Background-4" data-name="Background" class="cls-6" cx="213.37" cy="206.39" r="55"/> + <circle id="Stick-2" data-name="Stick" class="cls-1" cx="213.37" cy="206.39" r="45" transform="translate(-24.53 383.95) rotate(-80.78)"/> + </g> + <g id="Minus_Button" data-name="Minus Button"> + <circle id="_Background" data-name=" Background" class="cls-5" cx="374.17" cy="130.89" r="22.5"/> + </g> + <g id="Plus_Button" data-name="Plus Button"> + <circle id="_Background-2" data-name=" Background" class="cls-5" cx="623.57" cy="131.19" r="22.5"/> + </g> + <g id="Home_Button" data-name="Home Button"> + <circle id="_Background-3" data-name=" Background" class="cls-5" cx="578.57" cy="206.39" r="22.5"/> + </g> + <g id="Capture_Button" data-name="Capture Button"> + <path class="cls-5" d="m441.77,228.09h-30c-2.8,0-5-2.2-5-5v-29.5c0-2.8,2.2-5,5-5h30c2.8,0,5,2.2,5,5v29.5c0,2.7-2.2,5-5,5Z"/> + </g> + <g id="Buttons"> + <g id="A_Button" data-name="A Button"> + <circle id="Background-5" data-name="Background" class="cls-4" cx="837.07" cy="206.39" r="35"/> + </g> + <g id="X_Button" data-name="X Button"> + <circle id="Background-6" data-name="Background" class="cls-4" cx="767.07" cy="136.39" r="35"/> + </g> + <g id="Y_Button" data-name="Y Button"> + <circle id="Background-7" data-name="Background" class="cls-4" cx="697.07" cy="206.39" r="35"/> + </g> + <g id="B_Button" data-name="B Button"> + <circle id="Background-8" data-name="Background" class="cls-4" cx="767.07" cy="276.39" r="35"/> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json new file mode 100644 index 00000000..7cdcdf5a --- /dev/null +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -0,0 +1,656 @@ +{ + "Language": "Deutsch", + "MenuBarFileOpenApplet": "Öffne Anwendung", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii-Editor-Applet im Standalone-Modus", + "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", + "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", + "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", + "MenuBarFile": "_Datei", + "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", + "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", + "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", + "MenuBarFileExit": "_Beenden", + "MenuBarOptions": "Optionen", + "MenuBarOptionsToggleFullscreen": "Vollbild", + "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", + "MenuBarOptionsStopEmulation": "Emulation beenden", + "MenuBarOptionsSettings": "_Einstellungen", + "MenuBarOptionsManageUserProfiles": "_Benutzerprofile verwalten", + "MenuBarActions": "_Aktionen", + "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", + "MenuBarActionsScanAmiibo": "Amiibo scannen", + "MenuBarTools": "_Werkzeuge", + "MenuBarToolsInstallFirmware": "Firmware installieren", + "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", + "MenuBarToolsManageFileTypes": "Dateitypen verwalten", + "MenuBarToolsInstallFileTypes": "Dateitypen installieren", + "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", + "MenuBarHelp": "Hilfe", + "MenuBarHelpCheckForUpdates": "Nach Updates suchen", + "MenuBarHelpAbout": "Über Ryujinx", + "MenuSearch": "Suchen...", + "GameListHeaderFavorite": "Favorit", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Entwickler", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Spielzeit", + "GameListHeaderLastPlayed": "Zuletzt gespielt", + "GameListHeaderFileExtension": "Dateiformat", + "GameListHeaderFileSize": "Dateigröße", + "GameListHeaderPath": "Pfad", + "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", + "GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", + "GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT-Cache des Spiels beinhaltet", + "GameListContextMenuManageTitleUpdates": "Verwalte Spiel-Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", + "GameListContextMenuManageDlc": "Verwalten von DLC", + "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", + "GameListContextMenuCacheManagement": "Cache Verwaltung", + "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader-Cache der Anwendung", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC-Cache der Anwendung beinhaltet", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader-Cache-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", + "GameListContextMenuExtractData": "Daten extrahieren", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", + "StatusBarSystemVersion": "Systemversion: {0}", + "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", + "LinuxVmMaxMapCountDialogTextPrimary": "Möchtest Du den Wert von vm.max_map_count auf {0} erhöhen", + "LinuxVmMaxMapCountDialogTextSecondary": "Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ja, bis zum nächsten Neustart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ja, permanent", + "LinuxVmMaxMapCountWarningTextPrimary": "Maximale Anzahl an Speicherzuordnungen ist niedriger als empfohlen.", + "LinuxVmMaxMapCountWarningTextSecondary": "Der aktuelle Wert von vm.max_map_count ({0}) ist kleiner als {1}. Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.\n\nDu kannst das Limit entweder manuell erhöhen oder pkexec installieren, damit Ryujinx Dir dabei hilft.", + "Settings": "Einstellungen", + "SettingsTabGeneral": "Oberfläche", + "SettingsTabGeneralGeneral": "Allgemein", + "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", + "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", + "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", + "SettingsTabGeneralHideCursorNever": "Niemals", + "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", + "SettingsTabGeneralHideCursorAlways": "Immer", + "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", + "SettingsTabGeneralAdd": "Hinzufügen", + "SettingsTabGeneralRemove": "Entfernen", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Kern", + "SettingsTabSystemSystemRegion": "Systemregion:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australien", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Systemsprache:", + "SettingsTabSystemSystemLanguageJapanese": "Japanisch", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", + "SettingsTabSystemSystemLanguageFrench": "Französisch", + "SettingsTabSystemSystemLanguageGerman": "Deutsch", + "SettingsTabSystemSystemLanguageItalian": "Italienisch", + "SettingsTabSystemSystemLanguageSpanish": "Spanisch", + "SettingsTabSystemSystemLanguageChinese": "Chinesisch", + "SettingsTabSystemSystemLanguageKorean": "Koreanisch", + "SettingsTabSystemSystemLanguageDutch": "Niederländisch", + "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", + "SettingsTabSystemSystemLanguageRussian": "Russisch", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", + "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", + "SettingsTabSystemSystemTimeZone": "System-Zeitzone:", + "SettingsTabSystemSystemTime": "Systemzeit:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung", + "SettingsTabSystemAudioBackend": "Audio-Backend:", + "SettingsTabSystemAudioBackendDummy": "Ohne Funktion", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", + "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", + "SettingsTabGraphics": "Grafik", + "SettingsTabGraphicsAPI": "Grafik-API", + "SettingsTabGraphicsEnableShaderCache": "Shader-Cache aktivieren", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", + "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", + "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "An Fenster anpassen", + "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", + "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader-Dump-Pfad:", + "SettingsTabLogging": "Logs", + "SettingsTabLoggingLogging": "Logs", + "SettingsTabLoggingEnableLoggingToFile": "Protokollierung in Datei aktivieren", + "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", + "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", + "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", + "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", + "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", + "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", + "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen", + "SettingsTabLoggingDeveloperOptionsNote": "ACHTUNG: Wird die Leistung reduzieren", + "SettingsTabLoggingGraphicsBackendLogLevel": "Protokollstufe des Grafik-Backends:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle", + "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", + "SettingsTabInput": "Eingabe", + "SettingsTabInputEnableDockedMode": "Angedockter Modus", + "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", + "SettingsButtonSave": "Speichern", + "SettingsButtonClose": "Schließen", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Abbrechen", + "SettingsButtonApply": "Übernehmen", + "ControllerSettingsPlayer": "Spieler", + "ControllerSettingsPlayer1": "Spieler 1", + "ControllerSettingsPlayer2": "Spieler 2", + "ControllerSettingsPlayer3": "Spieler 3", + "ControllerSettingsPlayer4": "Spieler 4", + "ControllerSettingsPlayer5": "Spieler 5", + "ControllerSettingsPlayer6": "Spieler 6", + "ControllerSettingsPlayer7": "Spieler 7", + "ControllerSettingsPlayer8": "Spieler 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Eingabegerät", + "ControllerSettingsRefresh": "Aktualisieren", + "ControllerSettingsDeviceDisabled": "Deaktiviert", + "ControllerSettingsControllerType": "Controller-Typ", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Joy-Con-Paar", + "ControllerSettingsControllerTypeJoyConLeft": "Linker Joy-Con", + "ControllerSettingsControllerTypeJoyConRight": "Rechter Joy-Con", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Standard", + "ControllerSettingsLoad": "Laden", + "ControllerSettingsAdd": "Hinzufügen", + "ControllerSettingsRemove": "Entfernen", + "ControllerSettingsButtons": "Aktionstasten", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Steuerkreuz", + "ControllerSettingsDPadUp": "Hoch", + "ControllerSettingsDPadDown": "Runter", + "ControllerSettingsDPadLeft": "Links", + "ControllerSettingsDPadRight": "Rechts", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Hoch", + "ControllerSettingsStickDown": "Runter", + "ControllerSettingsStickLeft": "Links", + "ControllerSettingsStickRight": "Rechts", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "X-Achse invertieren", + "ControllerSettingsStickInvertYAxis": "Y-Achse invertieren", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Linker Analogstick", + "ControllerSettingsRStick": "Rechter Analogstick", + "ControllerSettingsTriggersLeft": "Linker Trigger", + "ControllerSettingsTriggersRight": "Rechter Trigger", + "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", + "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", + "ControllerSettingsTriggers": "Trigger", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", + "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", + "ControllerSettingsMisc": "Verschiedenes", + "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", + "ControllerSettingsMotion": "Bewegung", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", + "ControllerSettingsMotionControllerSlot": "Controller-Slot:", + "ControllerSettingsMotionMirrorInput": "Eingabe spiegeln", + "ControllerSettingsMotionRightJoyConSlot": "Rechter Joy-Con-Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro-Empfindlichkeit:", + "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", + "ControllerSettingsSave": "Speichern", + "ControllerSettingsClose": "Schließen", + "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", + "UserProfilesSaveProfileName": "Profilname speichern", + "UserProfilesChangeProfileImage": "Profilbild ändern", + "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", + "UserProfilesAddNewProfile": "Neues Profil", + "UserProfilesDelete": "Löschen", + "UserProfilesClose": "Schließen", + "ProfileNameSelectionWatermark": "Wähle einen Spitznamen", + "ProfileImageSelectionTitle": "Auswahl des Profilbildes", + "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", + "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", + "ProfileImageSelectionImportImage": "Bilddatei importieren", + "ProfileImageSelectionSelectAvatar": "Firmware-Avatar auswählen", + "InputDialogTitle": "Eingabe-Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Abbrechen", + "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", + "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", + "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", + "AvatarChoose": "Bestätigen", + "AvatarSetBackgroundColor": "Hintergrundfarbe auswählen", + "AvatarClose": "Schließen", + "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", + "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", + "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", + "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", + "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", + "MenuBarFileToolsHideUi": "Oberfläche ausblenden", + "GameListContextMenuRunApplication": "Anwendung ausführen", + "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", + "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", + "SettingsTabGeneralTheme": "Design", + "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", + "SettingsTabGeneralThemeBaseStyle": "Farbschema", + "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", + "SettingsTabGeneralThemeBaseStyleLight": "Hell", + "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", + "ButtonBrowse": "Durchsuchen", + "ControllerSettingsConfigureGeneral": "Konfigurieren", + "ControllerSettingsRumble": "Vibration", + "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", + "ControllerSettingsRumbleWeakMultiplier": "Schwacher Vibrations-Multiplikator", + "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Sollen Speicherdaten für dieses Spiel erstellt werden?", + "DialogConfirmationTitle": "Ryujinx - Bestätigung", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Fehler", + "DialogWarningTitle": "Ryujinx - Warnung", + "DialogExitTitle": "Ryujinx - Beenden", + "DialogErrorMessage": "Ein Fehler ist aufgetreten", + "DialogExitMessage": "Ryujinx wirklich schließen?", + "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", + "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", + "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Suchen der angegebenen Speicherdaten aufgetreten: {0}", + "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", + "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", + "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", + "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", + "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", + "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", + "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", + "DialogUpdaterDownloadingMessage": "Update wird heruntergeladen...", + "DialogUpdaterExtractionMessage": "Update wird entpackt...", + "DialogUpdaterRenamingMessage": "Update wird umbenannt...", + "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterCompleteMessage": "Update abgeschlossen!", + "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", + "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", + "DialogUpdaterArchNotSupportedSubMessage": "Nur 64-Bit-Systeme werden unterstützt!", + "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", + "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", + "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", + "DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/", + "DialogRestartRequiredMessage": "Neustart erforderlich", + "DialogThemeRestartMessage": "Das Design wurde gespeichert. Ein Neustart ist erforderlich, um das Design anzuwenden.", + "DialogThemeRestartSubMessage": "Jetzt neu starten?", + "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", + "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", + "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", + "DialogInstallFileTypesSuccessMessage": "Dateitypen erfolgreich installiert!", + "DialogInstallFileTypesErrorMessage": "Dateitypen konnten nicht installiert werden.", + "DialogUninstallFileTypesSuccessMessage": "Dateitypen erfolgreich deinstalliert!", + "DialogUninstallFileTypesErrorMessage": "Deinstallation der Dateitypen fehlgeschlagen.", + "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogControllerAppletTitle": "Controller-Applet", + "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", + "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", + "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", + "DialogAmiiboApiTitle": "Amiibo-API", + "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", + "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", + "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", + "DialogProfileDeleteProfileTitle": "Profil löschen", + "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", + "DialogWarning": "Warnung", + "DialogPPTCDeletionMessage": "Du bist dabei den PPTC für das folgende Spiel als ungültig zu markieren:\n\n{0}\n\nWirklich fortfahren?", + "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", + "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", + "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", + "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", + "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", + "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", + "DialogUserProfileUnsavedChangesTitle": "Warnung - Nicht gespeicherte Änderungen", + "DialogUserProfileUnsavedChangesMessage": "Sie haben Änderungen an diesem Nutzerprofil vorgenommen, die nicht gespeichert wurden.", + "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", + "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", + "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", + "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", + "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", + "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", + "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", + "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Aus", + "CommonOn": "An", + "InputDialogYes": "Ja", + "InputDialogNo": "Nein", + "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Fortsetzen", + "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", + "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", + "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", + "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", + "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", + "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", + "AboutRyujinxAboutTitle": "Über:", + "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", + "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", + "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboCharacterLabel": "Charakter", + "AmiiboScanButtonLabel": "Einscannen", + "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", + "DlcManagerTableHeadingEnabledLabel": "Aktiviert", + "DlcManagerTableHeadingTitleIdLabel": "Title-ID", + "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", + "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", + "DlcManagerRemoveAllButton": "Entferne alle", + "DlcManagerEnableAllButton": "Alle aktivieren", + "DlcManagerDisableAllButton": "Alle deaktivieren", + "MenuBarOptionsChangeLanguage": "Sprache ändern", + "MenuBarShowFileTypes": "Dateitypen anzeigen", + "CommonSort": "Sortieren", + "CommonShowNames": "Spiel-Namen anzeigen", + "CommonFavorite": "Favoriten", + "OrderAscending": "Aufsteigend", + "OrderDescending": "Absteigend", + "SettingsTabGraphicsFeatures": "Erweiterungen", + "ErrorWindowTitle": "Fehler-Fenster", + "ToggleDiscordTooltip": "Zeige momentanes Spiel auf Discord", + "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", + "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", + "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", + "CustomThemeCheckTooltip": "Verwende ein eigenes Design für die Emulator-Benutzeroberfläche", + "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", + "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", + "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", + "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", + "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", + "RegionTooltip": "Ändert die Systemregion", + "LanguageTooltip": "Ändert die Systemsprache", + "TimezoneTooltip": "Ändert die Systemzeitzone", + "TimeTooltip": "Ändert die Systemzeit", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", + "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", + "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", + "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", + "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", + "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", + "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", + "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", + "UseHypervisorTooltip": "Verwende Hypervisor anstelle von JIT. Verbessert die Leistung stark, falls vorhanden, kann jedoch in seinem aktuellen Zustand instabil sein.", + "DRamTooltip": "Erhöht den Arbeitsspeicher des emulierten Systems von 4 GiB auf 6 GiB.\n\nDies ist nur für Texturenpakete mit höherer Auflösung oder Mods mit 4K-Auflösung nützlich. Diese Option verbessert NICHT die Leistung.\n\nIm Zweifelsfall AUS lassen.", + "IgnoreMissingServicesTooltip": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.", + "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", + "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", + "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", + "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", + "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", + "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", + "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", + "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "InfoLogTooltip": "Ausgabe von Info-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "WarnLogTooltip": "Ausgabe von Warn-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "ErrorLogTooltip": "Ausgabe von Fehler-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "TraceLogTooltip": "Ausgabe von Trace-Log in der Konsole. Hat keinen Einfluss auf die Leistung.", + "GuestLogTooltip": "Ausgabe von Gast-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "FileAccessLogTooltip": "Ausgabe von FS-Zugriff-Logs in der Konsole.", + "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", + "DeveloperOptionTooltip": "Mit Vorsicht verwenden", + "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", + "DebugLogTooltip": "Ausgabe von Debug-Logs in der Konsole.\n\nVerwende diese Option nur auf ausdrückliche Anweisung von Ryujinx Entwicklern, da sie das Lesen der Protokolle erschwert und die Leistung des Emulators verschlechtert.", + "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", + "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", + "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", + "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", + "ExitTooltip": "Beendet Ryujinx", + "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", + "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", + "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", + "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", + "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", + "GridSize": "Rastergröße", + "GridSizeTooltip": "Ändert die Größe der Rasterelemente", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", + "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", + "SettingsTabSystemAudioVolume": "Lautstärke: ", + "AudioVolumeTooltip": "Ändert die Lautstärke", + "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", + "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", + "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", + "GameListContextMenuManageCheat": "Cheats verwalten", + "ControllerSettingsStickRange": "Bereich:", + "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", + "DialogStopEmulationMessage": "Emulation wirklich beenden?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Netzwerk", + "SettingsTabNetworkConnection": "Netwerkverbindung", + "SettingsTabCpuCache": "CPU-Cache", + "SettingsTabCpuMemory": "CPU-Speicher", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", + "UpdaterDisabledWarningTitle": "Updater deaktiviert!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", + "ControllerSettingsRotate90": "Um 90° rotieren", + "IconSize": "Cover Größe", + "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", + "MenuBarOptionsShowConsole": "Zeige Konsole", + "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", + "UserErrorNoKeys": "Keys nicht gefunden", + "UserErrorNoFirmware": "Firmware nicht gefunden", + "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", + "UserErrorApplicationNotFound": "Anwendung nicht gefunden", + "UserErrorUnknown": "Unbekannter Fehler", + "UserErrorUndefined": "Undefinierter Fehler", + "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", + "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", + "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", + "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", + "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", + "NoUpdate": "Kein Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Bestätigung", + "FileDialogAllTypes": "Alle Typen", + "Never": "Niemals", + "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", + "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "SoftwareKeyboard": "Software-Tastatur", + "SoftwareKeyboardModeNumbersOnly": "Nur Zahlen", + "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", + "SoftwareKeyboardModeASCII": "Nur ASCII-Text", + "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", + "UpdaterRenaming": "Alte Dateien umbenennen...", + "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", + "UpdaterAddingFiles": "Neue Dateien hinzufügen...", + "UpdaterExtracting": "Update extrahieren...", + "UpdaterDownloading": "Update herunterladen...", + "Game": "Spiel", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Verbindungsfehler.", + "AboutPageDeveloperListMore": "{0} und mehr...", + "ApiError": "API Fehler.", + "LoadingHeading": "{0} wird gestartet", + "CompilingPPTC": "PTC wird kompiliert", + "CompilingShaders": "Shader werden kompiliert", + "AllKeyboards": "Alle Tastaturen", + "OpenFileDialogTitle": "Wähle eine unterstützte Datei", + "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", + "AllSupportedFormats": "Alle unterstützten Formate", + "RyujinxUpdater": "Ryujinx - Updater", + "SettingsTabHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", + "SettingsTabHotkeysPauseHotkey": "Pausieren:", + "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", + "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", + "ControllerRumbleTitle": "Vibration - Einstellungen", + "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche", + "SettingsXamlThemeFile": "Xaml Design-Datei", + "AvatarWindowTitle": "Profile verwalten - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unbekannt", + "Usage": "Nutzung", + "Writable": "Beschreibbar", + "SelectDlcDialogTitle": "DLC-Dateien auswählen", + "SelectUpdateDialogTitle": "Update-Datei auswählen", + "UserProfileWindowTitle": "Benutzerprofile verwalten", + "CheatWindowTitle": "Spiel-Cheats verwalten", + "DlcWindowTitle": "Spiel-DLC verwalten", + "UpdateWindowTitle": "Spiel-Updates verwalten", + "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "UserProfilesEditProfile": "Profil bearbeiten", + "Cancel": "Abbrechen", + "Save": "Speichern", + "Discard": "Verwerfen", + "UserProfilesSetProfileImage": "Profilbild einrichten", + "UserProfileEmptyNameError": "Name ist erforderlich", + "UserProfileNoImageError": "Bitte ein Profilbild auswählen", + "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", + "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "Benutzer Id:", + "SettingsTabGraphicsBackend": "Grafik-Backend:", + "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", + "SettingsEnableTextureRecompression": "Textur-Rekompression", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", + "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", + "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", + "SettingsGpuBackendRestartMessage": "Das Grafik-Backend oder die Grafikkarteneinstellungen wurden geändert. Ein Neustart ist erforderlich um diese Einstellungen anzuwenden.", + "SettingsGpuBackendRestartSubMessage": "Ryujinx jetzt neu starten?", + "RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?", + "SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:", + "SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:", + "SettingsEnableMacroHLE": "HLE Makros aktivieren", + "SettingsEnableMacroHLETooltip": "High-Level-Emulation von GPU-Makrocode.\n\nVerbessert die Leistung, kann aber in einigen Spielen zu Grafikfehlern führen.\n\nBei Unsicherheit AKTIVIEREN.", + "SettingsEnableColorSpacePassthrough": "Farbraum Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Weist das Vulkan-Backend an, Farbinformationen ohne Angabe eines Farbraums weiterzuleiten. Für Benutzer mit Wide-Gamut-Displays kann dies zu lebendigeren Farben führen, allerdings auf Kosten der Farbkorrektheit.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Speicherstände verwalten", + "DeleteUserSave": "Möchtest du den Spielerstand für dieses Spiel löschen?", + "IrreversibleActionNote": "Diese Option kann nicht rückgängig gemacht werden.", + "SaveManagerHeading": "Spielstände für {0} verwalten", + "SaveManagerTitle": "Speicherdaten Manager", + "Name": "Name", + "Size": "Größe", + "Search": "Suche", + "UserProfilesRecoverLostAccounts": "Konto wiederherstellen", + "Recover": "Wiederherstellen", + "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", + "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", + "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Spiel-Rendering an", + "GraphicsAALabel": "Antialiasing:", + "GraphicsScalingFilterLabel": "Skalierungsfilter:", + "GraphicsScalingFilterTooltip": "Ermöglicht Framebuffer-Skalierung", + "GraphicsScalingFilterLevelLabel": "Stufe", + "GraphicsScalingFilterLevelTooltip": "Skalierungsfilter-Stufe festlegen", + "SmaaLow": "SMAA Niedrig", + "SmaaMedium": "SMAA Mittel", + "SmaaHigh": "SMAA Hoch", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Nutzer bearbeiten", + "UserEditorTitleCreate": "Nutzer erstellen", + "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", + "NetworkInterfaceTooltip": "Die Netzwerkschnittstelle, die für LAN-Funktionen verwendet wird", + "NetworkInterfaceDefault": "Standard", + "PackagingShaders": "Verpackt Shader", + "AboutChangelogButton": "Changelog in GitHub öffnen", + "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json new file mode 100644 index 00000000..59f50ad9 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -0,0 +1,656 @@ +{ + "Language": "Ελληνικά", + "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", + "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", + "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", + "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", + "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", + "SettingsTabSystemUseHypervisor": "Χρήση Hypervisor", + "MenuBarFile": "_Αρχείο", + "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", + "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", + "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", + "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", + "MenuBarFileExit": "_Έξοδος", + "MenuBarOptions": "Επιλογές", + "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", + "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", + "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", + "MenuBarOptionsSettings": "_Ρυθμίσεις", + "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", + "MenuBarActions": "_Δράσεις", + "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", + "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarTools": "_Εργαλεία", + "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", + "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", + "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", + "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", + "MenuBarHelp": "Βοήθεια", + "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", + "MenuBarHelpAbout": "Σχετικά με", + "MenuSearch": "Αναζήτηση...", + "GameListHeaderFavorite": "Αγαπημένο", + "GameListHeaderIcon": "Εικονίδιο", + "GameListHeaderApplication": "Όνομα", + "GameListHeaderDeveloper": "Προγραμματιστής", + "GameListHeaderVersion": "Έκδοση", + "GameListHeaderTimePlayed": "Χρόνος", + "GameListHeaderLastPlayed": "Παίχτηκε", + "GameListHeaderFileExtension": "Κατάληξη", + "GameListHeaderFileSize": "Μέγεθος Αρχείου", + "GameListHeaderPath": "Τοποθεσία", + "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", + "GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", + "GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", + "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageDlc": "Διαχείριση DLC", + "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", + "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", + "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", + "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", + "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataLogo": "Λογότυπο", + "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", + "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Ρυθμίσεις", + "SettingsTabGeneral": "Εμφάνιση", + "SettingsTabGeneralGeneral": "Γενικά", + "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", + "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", + "SettingsTabGeneralHideCursorNever": "Ποτέ", + "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", + "SettingsTabGeneralHideCursorAlways": "Πάντα", + "SettingsTabGeneralGameDirectories": "Τοποθεσίες παιχνιδιών", + "SettingsTabGeneralAdd": "Προσθήκη", + "SettingsTabGeneralRemove": "Αφαίρεση", + "SettingsTabSystem": "Σύστημα", + "SettingsTabSystemCore": "Πυρήνας", + "SettingsTabSystemSystemRegion": "Περιοχή Συστήματος:", + "SettingsTabSystemSystemRegionJapan": "Ιαπωνία", + "SettingsTabSystemSystemRegionUSA": "ΗΠΑ", + "SettingsTabSystemSystemRegionEurope": "Ευρώπη", + "SettingsTabSystemSystemRegionAustralia": "Αυστραλία", + "SettingsTabSystemSystemRegionChina": "Κίνα", + "SettingsTabSystemSystemRegionKorea": "Κορέα", + "SettingsTabSystemSystemRegionTaiwan": "Ταϊβάν", + "SettingsTabSystemSystemLanguage": "Γλώσσα Συστήματος:", + "SettingsTabSystemSystemLanguageJapanese": "Ιαπωνικά", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Αμερικάνικα Αγγλικά", + "SettingsTabSystemSystemLanguageFrench": "Γαλλικά", + "SettingsTabSystemSystemLanguageGerman": "Γερμανικά", + "SettingsTabSystemSystemLanguageItalian": "Ιταλικά", + "SettingsTabSystemSystemLanguageSpanish": "Ισπανικά", + "SettingsTabSystemSystemLanguageChinese": "Κινέζικα", + "SettingsTabSystemSystemLanguageKorean": "Κορεάτικα", + "SettingsTabSystemSystemLanguageDutch": "Ολλανδικά", + "SettingsTabSystemSystemLanguagePortuguese": "Πορτογαλικά", + "SettingsTabSystemSystemLanguageRussian": "Ρώσικα", + "SettingsTabSystemSystemLanguageTaiwanese": "Ταϊβανέζικα", + "SettingsTabSystemSystemLanguageBritishEnglish": "Βρετανικά Αγγλικά", + "SettingsTabSystemSystemLanguageCanadianFrench": "Καναδικά Γαλλικά", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Λατινοαμερικάνικα Ισπανικά", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Απλοποιημένα Κινέζικα", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Παραδοσιακά Κινεζικά", + "SettingsTabSystemSystemTimeZone": "Ζώνη Ώρας Συστήματος:", + "SettingsTabSystemSystemTime": "Ώρα Συστήματος:", + "SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", + "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", + "SettingsTabSystemAudioBackend": "Backend Ήχου:", + "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Μικροδιορθώσεις", + "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", + "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", + "SettingsTabGraphics": "Γραφικά", + "SettingsTabGraphicsAPI": "API Γραφικά", + "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", + "SettingsTabGraphicsAnisotropicFiltering": "Ανισότροπο Φιλτράρισμα:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Αυτόματο", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Κλίμακα Ανάλυσης:", + "SettingsTabGraphicsResolutionScaleCustom": "Προσαρμοσμένο (Δεν συνιστάται)", + "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Έκταση σε όλο το παράθυρο", + "SettingsTabGraphicsDeveloperOptions": "Επιλογές Προγραμματιστή", + "SettingsTabGraphicsShaderDumpPath": "Τοποθεσία Shaders Γραφικών:", + "SettingsTabLogging": "Καταγραφή", + "SettingsTabLoggingLogging": "Καταγραφή", + "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", + "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", + "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", + "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", + "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", + "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", + "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", + "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", + "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", + "SettingsTabLoggingDeveloperOptionsNote": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Θα μειώσει την απόδοση", + "SettingsTabLoggingGraphicsBackendLogLevel": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα", + "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", + "SettingsTabInput": "Χειρισμός", + "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", + "SettingsButtonSave": "Αποθήκευση", + "SettingsButtonClose": "Κλείσιμο", + "SettingsButtonOk": "ΟΚ", + "SettingsButtonCancel": "Ακύρωση", + "SettingsButtonApply": "Εφαρμογή", + "ControllerSettingsPlayer": "Παίχτης", + "ControllerSettingsPlayer1": "Παίχτης 1", + "ControllerSettingsPlayer2": "Παίχτης 2", + "ControllerSettingsPlayer3": "Παίχτης 3", + "ControllerSettingsPlayer4": "Παίχτης 4", + "ControllerSettingsPlayer5": "Παίχτης 5", + "ControllerSettingsPlayer6": "Παίχτης 6", + "ControllerSettingsPlayer7": "Παίχτης 7", + "ControllerSettingsPlayer8": "Παίχτης 8", + "ControllerSettingsHandheld": "Χειροκίνητο", + "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", + "ControllerSettingsRefresh": "Ανανέωση", + "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", + "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", + "ControllerSettingsControllerTypeHandheld": "Φορητό", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", + "ControllerSettingsProfile": "Προφίλ", + "ControllerSettingsProfileDefault": "Προκαθορισμένο", + "ControllerSettingsLoad": "Φόρτωση", + "ControllerSettingsAdd": "Προσθήκη", + "ControllerSettingsRemove": "Αφαίρεση", + "ControllerSettingsButtons": "Κουμπιά", + "ControllerSettingsButtonA": "Α", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Κατευθυντικό Pad", + "ControllerSettingsDPadUp": "Πάνω", + "ControllerSettingsDPadDown": "Κάτω", + "ControllerSettingsDPadLeft": "Αριστερά", + "ControllerSettingsDPadRight": "Δεξιά", + "ControllerSettingsStickButton": "Κουμπί", + "ControllerSettingsStickUp": "Πάνω", + "ControllerSettingsStickDown": "Κάτω", + "ControllerSettingsStickLeft": "Αριστερά", + "ControllerSettingsStickRight": "Δεξιά", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Αριστερός Μοχλός", + "ControllerSettingsRStick": "Δεξιός Μοχλός", + "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", + "ControllerSettingsTriggersRight": "Δεξιά Σκανδάλη", + "ControllerSettingsTriggersButtonsLeft": "Αριστερά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggersButtonsRight": "Δεξιά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggers": "Σκανδάλες", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Αριστερά Κουμπιά", + "ControllerSettingsExtraButtonsRight": "Δεξιά Κουμπιά", + "ControllerSettingsMisc": "Διάφορα", + "ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:", + "ControllerSettingsMotion": "Κίνηση", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", + "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", + "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", + "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", + "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", + "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", + "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", + "ControllerSettingsSave": "Αποθήκευση", + "ControllerSettingsClose": "Κλείσιμο", + "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", + "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", + "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", + "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", + "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", + "UserProfilesDelete": "Διαγράφω", + "UserProfilesClose": "Κλείσιμο", + "ProfileNameSelectionWatermark": "Επιλέξτε ψευδώνυμο", + "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", + "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", + "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", + "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", + "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", + "InputDialogTitle": "Διάλογος Εισαγωγής", + "InputDialogOk": "ΟΚ", + "InputDialogCancel": "Ακύρωση", + "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", + "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", + "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", + "AvatarChoose": "Επιλογή", + "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", + "AvatarClose": "Κλείσιμο", + "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", + "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", + "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", + "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", + "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", + "MenuBarFileToolsHideUi": "Απόκρυψη UI", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", + "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", + "SettingsTabGeneralTheme": "Θέμα", + "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", + "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", + "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", + "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", + "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", + "ButtonBrowse": "Αναζήτηση", + "ControllerSettingsConfigureGeneral": "Παραμέτρων", + "ControllerSettingsRumble": "Δόνηση", + "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", + "ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης", + "DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Θέλετε να αποθηκεύσετε δεδομένα για αυτό το παιχνίδι;", + "DialogConfirmationTitle": "Ryujinx - Επιβεβαίωση", + "DialogUpdaterTitle": "Ryujinx - Ενημερωτής", + "DialogErrorTitle": "Ryujinx - Σφάλμα", + "DialogWarningTitle": "Ryujinx - Προειδοποίηση", + "DialogExitTitle": "Ryujinx - Έξοδος", + "DialogErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogExitMessage": "Είστε βέβαιοι ότι θέλετε να κλείσετε το Ryujinx;", + "DialogExitSubMessage": "Όλα τα μη αποθηκευμένα δεδομένα θα χαθούν!", + "DialogMessageCreateSaveErrorMessage": "Σφάλμα κατά τη δημιουργία των αποθηκευμένων δεδομένων: {0}", + "DialogMessageFindSaveErrorMessage": "Σφάλμα κατά την εύρεση των αποθηκευμένων δεδομένων: {0}", + "FolderDialogExtractTitle": "Επιλέξτε τον φάκελο στον οποίο θέλετε να εξαγάγετε", + "DialogNcaExtractionMessage": "Εξαγωγή ενότητας {0} από {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Εξαγωγέας Τμημάτων", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Αποτυχία εξαγωγής. Η κύρια NCA δεν υπήρχε στο επιλεγμένο αρχείο.", + "DialogNcaExtractionCheckLogErrorMessage": "Αποτυχία εξαγωγής. Διαβάστε το αρχείο καταγραφής για περισσότερες πληροφορίες.", + "DialogNcaExtractionSuccessMessage": "Η εξαγωγή ολοκληρώθηκε με επιτυχία.", + "DialogUpdaterConvertFailedMessage": "Αποτυχία μετατροπής της τρέχουσας έκδοσης Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Ακύρωση Ενημέρωσης!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Προέκυψε ένα σφάλμα στη λήψη πληροφοριών έκδοσης από τα GitHub Releases. Αυτό δύναται να συμβεί αν μία έκδοση χτίζεται αυτή τη στιγμή στα GitHub Actions. Παρακαλούμε προσπαθήστε αργότερα.", + "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", + "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", + "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", + "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", + "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", + "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", + "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", + "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", + "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", + "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", + "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", + "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", + "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", + "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", + "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", + "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", + "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", + "DialogInstallFileTypesErrorMessage": "Απέτυχε η εγκατάσταση τύπων αρχείων.", + "DialogUninstallFileTypesSuccessMessage": "Επιτυχής απεγκατάσταση τύπων αρχείων!", + "DialogUninstallFileTypesErrorMessage": "Αποτυχία απεγκατάστασης τύπων αρχείων.", + "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogControllerAppletTitle": "Applet Χειρισμού", + "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", + "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", + "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", + "DialogAmiiboApiTitle": "API για Amiibo.", + "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", + "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", + "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", + "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", + "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogWarning": "Προειδοποίηση", + "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", + "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", + "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", + "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", + "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", + "DialogUserProfileUnsavedChangesTitle": "Προσοχή - Μην Αποθηκευμένες Αλλαγές.", + "DialogUserProfileUnsavedChangesMessage": "Έχετε κάνει αλλαγές σε αυτό το προφίλ χρήστη που δεν έχουν αποθηκευτεί.", + "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", + "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", + "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", + "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", + "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", + "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", + "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", + "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", + "CommonAuto": "Αυτόματο", + "CommonOff": "Ανενεργό", + "CommonOn": "Ενεργό", + "InputDialogYes": "Ναι", + "InputDialogNo": "Όχι", + "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", + "MenuBarOptionsPauseEmulation": "Παύση", + "MenuBarOptionsResumeEmulation": "Συνέχιση", + "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", + "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", + "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxAboutTitle": "Σχετικά με:", + "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", + "AboutRyujinxMaintainersTitle": "Συντηρείται από:", + "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", + "AmiiboSeriesLabel": "Σειρά Amiibo", + "AmiiboCharacterLabel": "Χαρακτήρας", + "AmiiboScanButtonLabel": "Σαρώστε το", + "AmiiboOptionsShowAllLabel": "Εμφάνιση όλων των Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", + "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιημένο", + "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", + "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", + "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", + "DlcManagerRemoveAllButton": "Αφαίρεση όλων", + "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", + "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", + "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", + "CommonSort": "Κατάταξη", + "CommonShowNames": "Εμφάνιση ονομάτων", + "CommonFavorite": "Αγαπημένα", + "OrderAscending": "Αύξουσα", + "OrderDescending": "Φθίνουσα", + "SettingsTabGraphicsFeatures": "Χαρακτηριστικά & Βελτιώσεις", + "ErrorWindowTitle": "Παράθυρο σφάλματος", + "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", + "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", + "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", + "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", + "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", + "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", + "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", + "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", + "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", + "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", + "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", + "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", + "TimeTooltip": "Αλλαγή Ώρας Συστήματος", + "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", + "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", + "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", + "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", + "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", + "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", + "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", + "UseHypervisorTooltip": "Χρησιμοποιήστε Hypervisor αντί για JIT. Βελτιώνει σημαντικά την απόδοση όταν διατίθεται, αλλά μπορεί να είναι ασταθής στην τρέχουσα κατάστασή του.", + "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB", + "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", + "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", + "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", + "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", + "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", + "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", + "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", + "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", + "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", + "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", + "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", + "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", + "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", + "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", + "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", + "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", + "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", + "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", + "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", + "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", + "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", + "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", + "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", + "ExitTooltip": "Έξοδος από το Ryujinx", + "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", + "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", + "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", + "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", + "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", + "GridSize": "Μέγεθος Πλέγματος", + "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", + "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", + "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", + "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", + "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", + "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", + "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", + "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "ControllerSettingsStickRange": "Εύρος:", + "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", + "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", + "SettingsTabCpu": "Επεξεργαστής", + "SettingsTabAudio": "Ήχος", + "SettingsTabNetwork": "Δίκτυο", + "SettingsTabNetworkConnection": "Σύνδεση δικτύου", + "SettingsTabCpuCache": "Προσωρινή Μνήμη CPU", + "SettingsTabCpuMemory": "Μνήμη CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", + "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", + "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ανοίγει τον εναλλακτικό SD card Atmosphere κατάλογο που περιέχει Mods για το Application. Χρήσιμο για mods τα οποία συσκευάστηκαν για την πραγματική κονσόλα.", + "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", + "IconSize": "Μέγεθος Εικονιδίου", + "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", + "MenuBarOptionsShowConsole": "Εμφάνιση Κονσόλας", + "ShaderCachePurgeError": "Σφάλμα κατά την εκκαθάριση του shader cache στο {0}: {1}", + "UserErrorNoKeys": "Τα κλειδιά δεν βρέθηκαν", + "UserErrorNoFirmware": "Το firmware δε βρέθηκε", + "UserErrorFirmwareParsingFailed": "Σφάλμα ανάλυσης firmware", + "UserErrorApplicationNotFound": "Η εφαρμογή δε βρέθηκε", + "UserErrorUnknown": "Άγνωστο σφάλμα", + "UserErrorUndefined": "Αόριστο σφάλμα", + "UserErrorNoKeysDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο 'prod.keys'", + "UserErrorNoFirmwareDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware", + "UserErrorFirmwareParsingFailedDescription": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.", + "UserErrorApplicationNotFoundDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει έγκυρη εφαρμογή στη συγκεκριμένη διαδρομή.", + "UserErrorUnknownDescription": "Παρουσιάστηκε άγνωστο σφάλμα.", + "UserErrorUndefinedDescription": "Παρουσιάστηκε ένα άγνωστο σφάλμα! Αυτό δεν πρέπει να συμβεί, παρακαλώ επικοινωνήστε με έναν προγραμματιστή!", + "OpenSetupGuideMessage": "Ανοίξτε τον Οδηγό Εγκατάστασης.", + "NoUpdate": "Καμία Eνημέρωση", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Πληροφορίες", + "RyujinxConfirm": "Ryujinx - Επιβεβαίωση", + "FileDialogAllTypes": "Όλοι οι τύποι", + "Never": "Ποτέ", + "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", + "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", + "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "Η εφαρμογή ζητά {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", + "DialogControllerAppletMessage": "Η εφαρμογή ζητά ακριβώς {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις Ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", + "DialogControllerAppletDockModeSet": "Η κατάσταση Docked ενεργοποιήθηκε. Το συσκευή παλάμης είναι επίσης μην αποδεκτό.", + "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", + "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", + "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", + "UpdaterExtracting": "Εξαγωγή Ενημέρωσης...", + "UpdaterDownloading": "Λήψη Ενημέρωσης...", + "Game": "Παιχνίδι", + "Docked": "Προσκολλημένο", + "Handheld": "Χειροκίνητο", + "ConnectionError": "Σφάλμα Σύνδεσης.", + "AboutPageDeveloperListMore": "{0} και περισσότερα...", + "ApiError": "Σφάλμα API.", + "LoadingHeading": "Φόρτωση {0}", + "CompilingPPTC": "Μεταγλώττιση του PTC", + "CompilingShaders": "Σύνταξη των Shaders", + "AllKeyboards": "Όλα τα πληκτρολόγια", + "OpenFileDialogTitle": "Επιλέξτε ένα υποστηριζόμενο αρχείο για άνοιγμα", + "OpenFolderDialogTitle": "Επιλέξτε ένα φάκελο με ένα αποσυμπιεσμένο παιχνίδι", + "AllSupportedFormats": "Όλες Οι Υποστηριζόμενες Μορφές", + "RyujinxUpdater": "Ryujinx Ενημερωτής", + "SettingsTabHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysToggleVsyncHotkey": "Εναλλαγή VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Στιγμιότυπο Οθόνης:", + "SettingsTabHotkeysShowUiHotkey": "Εμφάνιση Διεπαφής Χρήστη:", + "SettingsTabHotkeysPauseHotkey": "Παύση:", + "SettingsTabHotkeysToggleMuteHotkey": "Σίγαση:", + "ControllerMotionTitle": "Ρυθμίσεις Ελέγχου Κίνησης", + "ControllerRumbleTitle": "Ρυθμίσεις Δόνησης", + "SettingsSelectThemeFileDialogTitle": "Επιλογή Αρχείου Θέματος", + "SettingsXamlThemeFile": "Αρχείο Θέματος Xaml", + "AvatarWindowTitle": "Διαχείριση Λογαριασμών - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Άγνωστο", + "Usage": "Χρήση", + "Writable": "Εγγράψιμο", + "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", + "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", + "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", + "CheatWindowTitle": "Διαχειριστής των Cheats", + "DlcWindowTitle": "Downloadable Content Manager", + "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", + "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", + "Cancel": "Ακύρωση", + "Save": "Αποθήκευση", + "Discard": "Απόρριψη", + "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", + "UserProfileEmptyNameError": "Απαιτείται όνομα", + "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Αύξηση της ανάλυσης:", + "SettingsTabHotkeysResScaleDownHotkey": "Μείωση της ανάλυσης:", + "UserProfilesName": "Όνομα:", + "UserProfilesUserId": "User Id:", + "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", + "SettingsTabGraphicsBackendTooltip": "Backend Γραφικών που θα χρησιμοποιηθεί", + "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", + "SettingsEnableTextureRecompressionTooltip": "Συμπιέζει συγκεκριμένα texture για να μειωθεί η χρήση της VRAM.\nΣυνίσταται για χρήση σε κάρτες γραφικών με λιγότερο από 4 GiB VRAM.\nΑφήστε το Απενεργοποιημένο αν είστε αβέβαιοι.", + "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", + "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", + "SettingsGpuBackendRestartMessage": "Οι ρυθμίσεις GPU έχουν αλλαχτεί. Θα χρειαστεί επανεκκίνηση του Ryujinx για να τεθούν σε ισχύ.", + "SettingsGpuBackendRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση τώρα;", + "RyujinxUpdaterMessage": "Θέλετε να ενημερώσετε το Ryujinx στην πιο πρόσφατη έκδοση:", + "SettingsTabHotkeysVolumeUpHotkey": "Αύξηση Έντασης:", + "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", + "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", + "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Έντ.", + "UserProfilesManageSaves": "Διαχείριση Των Save", + "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", + "IrreversibleActionNote": "Αυτή η ενέργεια είναι μη αναστρέψιμη.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Διαχειριστής Save", + "Name": "Όνομα", + "Size": "Μέγεθος", + "Search": "Αναζήτηση", + "UserProfilesRecoverLostAccounts": "Ανάκτηση Χαμένων Λογαριασμών", + "Recover": "Ανάκτηση", + "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", + "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", + "GraphicsAATooltip": "Εφαρμόζει anti-aliasing στην απόδοση του παιχνιδιού", + "GraphicsAALabel": "Anti-Aliasing", + "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", + "GraphicsScalingFilterTooltip": "Ενεργοποιεί Κλίμακα Framebuffer", + "GraphicsScalingFilterLevelLabel": "Επίπεδο", + "GraphicsScalingFilterLevelTooltip": "Ορισμός Επιπέδου Φίλτρου Κλιμάκωσης", + "SmaaLow": "Χαμηλό SMAA", + "SmaaMedium": " Μεσαίο SMAA", + "SmaaHigh": "Υψηλό SMAA", + "SmaaUltra": "Oύλτρα SMAA", + "UserEditorTitle": "Επεξεργασία Χρήστη", + "UserEditorTitleCreate": "Δημιουργία Χρήστη", + "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", + "NetworkInterfaceTooltip": "Η διεπαφή δικτύου που χρησιμοποιείται για τα χαρακτηριστικά LAN", + "NetworkInterfaceDefault": "Προεπιλογή", + "PackagingShaders": "Shaders Συσκευασίας", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json new file mode 100644 index 00000000..2febf90e --- /dev/null +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -0,0 +1,668 @@ +{ + "Language": "English (US)", + "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", + "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", + "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Load Application From File", + "MenuBarFileOpenUnpacked": "Load _Unpacked Game", + "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", + "MenuBarFileOpenLogsFolder": "Open Logs Folder", + "MenuBarFileExit": "_Exit", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", + "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", + "MenuBarOptionsStopEmulation": "Stop Emulation", + "MenuBarOptionsSettings": "_Settings", + "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", + "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Install Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarHelp": "_Help", + "MenuBarHelpCheckForUpdates": "Check for Updates", + "MenuBarHelpAbout": "About", + "MenuSearch": "Search...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Developer", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Play Time", + "GameListHeaderLastPlayed": "Last Played", + "GameListHeaderFileExtension": "File Ext", + "GameListHeaderFileSize": "File Size", + "GameListHeaderPath": "Path", + "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", + "GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save", + "GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", + "GameListContextMenuManageTitleUpdates": "Manage Title Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", + "GameListContextMenuManageDlc": "Manage DLC", + "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", + "GameListContextMenuCacheManagement": "Cache Management", + "GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", + "GameListContextMenuExtractData": "Extract Data", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", + "GameListContextMenuCreateShortcut": "Create Application Shortcut", + "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} Games Loaded", + "StatusBarSystemVersion": "System Version: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Settings", + "SettingsTabGeneral": "User Interface", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", + "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralHideCursor": "Hide Cursor:", + "SettingsTabGeneralHideCursorNever": "Never", + "SettingsTabGeneralHideCursorOnIdle": "On Idle", + "SettingsTabGeneralHideCursorAlways": "Always", + "SettingsTabGeneralGameDirectories": "Game Directories", + "SettingsTabGeneralAdd": "Add", + "SettingsTabGeneralRemove": "Remove", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "System Language:", + "SettingsTabSystemSystemLanguageJapanese": "Japanese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", + "SettingsTabSystemSystemLanguageFrench": "French", + "SettingsTabSystemSystemLanguageGerman": "German", + "SettingsTabSystemSystemLanguageItalian": "Italian", + "SettingsTabSystemSystemLanguageSpanish": "Spanish", + "SettingsTabSystemSystemLanguageChinese": "Chinese", + "SettingsTabSystemSystemLanguageKorean": "Korean", + "SettingsTabSystemSystemLanguageDutch": "Dutch", + "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", + "SettingsTabSystemSystemLanguageRussian": "Russian", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "British English", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", + "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "Audio Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": "May cause instability", + "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)", + "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", + "SettingsTabGraphics": "Graphics", + "SettingsTabGraphicsAPI": "Graphics API", + "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", + "SettingsTabGraphicsDeveloperOptions": "Developer Options", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "Logging", + "SettingsTabLoggingLogging": "Logging", + "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", + "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "Developer Options", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "None", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Error", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "All", + "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", + "SettingsButtonSave": "Save", + "SettingsButtonClose": "Close", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancel", + "SettingsButtonApply": "Apply", + "ControllerSettingsPlayer": "Player", + "ControllerSettingsPlayer1": "Player 1", + "ControllerSettingsPlayer2": "Player 2", + "ControllerSettingsPlayer3": "Player 3", + "ControllerSettingsPlayer4": "Player 4", + "ControllerSettingsPlayer5": "Player 5", + "ControllerSettingsPlayer6": "Player 6", + "ControllerSettingsPlayer7": "Player 7", + "ControllerSettingsPlayer8": "Player 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Input Device", + "ControllerSettingsRefresh": "Refresh", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Controller Type", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", + "ControllerSettingsProfile": "Profile", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Load", + "ControllerSettingsAdd": "Add", + "ControllerSettingsRemove": "Remove", + "ControllerSettingsButtons": "Buttons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Up", + "ControllerSettingsDPadDown": "Down", + "ControllerSettingsDPadLeft": "Left", + "ControllerSettingsDPadRight": "Right", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Left Stick", + "ControllerSettingsRStick": "Right Stick", + "ControllerSettingsTriggersLeft": "Triggers Left", + "ControllerSettingsTriggersRight": "Triggers Right", + "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", + "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Buttons Left", + "ControllerSettingsExtraButtonsRight": "Buttons Right", + "ControllerSettingsMisc": "Miscellaneous", + "ControllerSettingsTriggerThreshold": "Trigger Threshold:", + "ControllerSettingsMotion": "Motion", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Mirror Input", + "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Save", + "ControllerSettingsClose": "Close", + "UserProfilesSelectedUserProfile": "Selected User Profile:", + "UserProfilesSaveProfileName": "Save Profile Name", + "UserProfilesChangeProfileImage": "Change Profile Image", + "UserProfilesAvailableUserProfiles": "Available User Profiles:", + "UserProfilesAddNewProfile": "Create Profile", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Close", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Profile Image Selection", + "ProfileImageSelectionHeader": "Choose a profile Image", + "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", + "ProfileImageSelectionImportImage": "Import Image File", + "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancel", + "InputDialogAddNewProfileTitle": "Choose the Profile Name", + "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", + "InputDialogAddNewProfileSubtext": "(Max Length: {0})", + "AvatarChoose": "Choose Avatar", + "AvatarSetBackgroundColor": "Set Background Color", + "AvatarClose": "Close", + "ControllerSettingsLoadProfileToolTip": "Load Profile", + "ControllerSettingsAddProfileToolTip": "Add Profile", + "ControllerSettingsRemoveProfileToolTip": "Remove Profile", + "ControllerSettingsSaveProfileToolTip": "Save Profile", + "MenuBarFileToolsTakeScreenshot": "Take Screenshot", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Toggle Favorite", + "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", + "ControllerSettingsConfigureGeneral": "Configure", + "ControllerSettingsRumble": "Rumble", + "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", + "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", + "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Warning", + "DialogExitTitle": "Ryujinx - Exit", + "DialogErrorMessage": "Ryujinx has encountered an error", + "DialogExitMessage": "Are you sure you want to close Ryujinx?", + "DialogExitSubMessage": "All unsaved data will be lost!", + "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", + "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", + "FolderDialogExtractTitle": "Choose the folder to extract into", + "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", + "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", + "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", + "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Downloading Update...", + "DialogUpdaterExtractionMessage": "Extracting Update...", + "DialogUpdaterRenamingMessage": "Renaming Update...", + "DialogUpdaterAddingFilesMessage": "Adding New Update...", + "DialogUpdaterCompleteMessage": "Update Complete!", + "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", + "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", + "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", + "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", + "DialogRestartRequiredMessage": "Restart Required", + "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", + "DialogThemeRestartSubMessage": "Do you want to restart", + "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", + "DialogFirmwareInstalledMessage": "Firmware {0} was installed", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Open Settings Window", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", + "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", + "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", + "DialogProfileDeleteProfileTitle": "Deleting Profile", + "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", + "DialogWarning": "Warning", + "DialogPPTCDeletionMessage": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?", + "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", + "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", + "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", + "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", + "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", + "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "Features", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Off", + "CommonOn": "On", + "InputDialogYes": "Yes", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Resume", + "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "About:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "Maintained By:", + "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", + "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", + "AmiiboSeriesLabel": "Amiibo Series", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "Scan It", + "AmiiboOptionsShowAllLabel": "Show All Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Enabled", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Path", + "DlcManagerTableHeadingFullPathLabel": "Full Path", + "DlcManagerRemoveAllButton": "Remove All", + "DlcManagerEnableAllButton": "Enable All", + "DlcManagerDisableAllButton": "Disable All", + "ModManagerDeleteAllButton": "Delete All", + "MenuBarOptionsChangeLanguage": "Change Language", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Sort", + "CommonShowNames": "Show Names", + "CommonFavorite": "Favorite", + "OrderAscending": "Ascending", + "OrderDescending": "Descending", + "SettingsTabGraphicsFeatures": "Features & Enhancements", + "ErrorWindowTitle": "Error Window", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "Enter a game directory to add to the list", + "AddGameDirTooltip": "Add a game directory to the list", + "RemoveGameDirTooltip": "Remove selected game directory", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "Path to custom GUI theme", + "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Change System Region", + "LanguageTooltip": "Change System Language", + "TimezoneTooltip": "Change System TimeZone", + "TimeTooltip": "Change System Time", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", + "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", + "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", + "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", + "FileAccessLogTooltip": "Prints file access log messages in the console.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Use with care", + "OpenGlLogLevel": "Requires appropriate log levels enabled", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load", + "LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load", + "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "Exit Ryujinx", + "OpenSettingsTooltip": "Open settings window", + "OpenProfileManagerTooltip": "Open User Profiles Manager window", + "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", + "CheckUpdatesTooltip": "Check for updates to Ryujinx", + "OpenAboutTooltip": "Open About Window", + "GridSize": "Grid Size", + "GridSizeTooltip": "Change the size of grid items", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", + "AboutRyujinxContributorsButtonHeader": "See All Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Change Audio Volume", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.", + "GameListContextMenuManageCheatToolTip": "Manage Cheats", + "GameListContextMenuManageCheat": "Manage Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", + "ControllerSettingsStickRange": "Range:", + "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", + "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Network", + "SettingsTabNetworkConnection": "Network Connection", + "SettingsTabCpuCache": "CPU Cache", + "SettingsTabCpuMemory": "CPU Mode", + "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Show UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Select DLC files", + "SelectUpdateDialogTitle": "Select update files", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s)", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "Paused": "Paused", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "Manage Updates for {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User ID:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0} ({1})", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edit User", + "UserEditorTitleCreate": "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Default", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json new file mode 100644 index 00000000..91bcd8f1 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -0,0 +1,656 @@ +{ + "Language": "Español (ES)", + "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", + "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", + "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar hipervisor", + "MenuBarFile": "_Archivo", + "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", + "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", + "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", + "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", + "MenuBarFileExit": "_Salir", + "MenuBarOptions": "_Opciones", + "MenuBarOptionsToggleFullscreen": "Cambiar a pantalla completa.", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", + "MenuBarOptionsStopEmulation": "Detener emulación", + "MenuBarOptionsSettings": "_Configuración", + "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", + "MenuBarActions": "_Acciones", + "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", + "MenuBarActionsScanAmiibo": "Escanear Amiibo", + "MenuBarTools": "_Herramientas", + "MenuBarToolsInstallFirmware": "Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", + "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", + "MenuBarHelp": "Ayuda", + "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", + "MenuBarHelpAbout": "Acerca de", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favoritos", + "GameListHeaderIcon": "Icono", + "GameListHeaderApplication": "Nombre", + "GameListHeaderDeveloper": "Desarrollador", + "GameListHeaderVersion": "Versión", + "GameListHeaderTimePlayed": "Tiempo jugado", + "GameListHeaderLastPlayed": "Jugado por última vez", + "GameListHeaderFileExtension": "Extensión", + "GameListHeaderFileSize": "Tamaño del archivo", + "GameListHeaderPath": "Directorio", + "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación", + "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", + "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", + "GameListContextMenuManageDlc": "Gestionar DLC", + "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", + "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", + "GameListContextMenuCacheManagement": "Gestión de caché ", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", + "GameListContextMenuExtractData": "Extraer datos", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataLogo": "Logotipo", + "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "StatusBarGamesLoaded": "{0}/{1} juegos cargados", + "StatusBarSystemVersion": "Versión del sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sí, hasta el próximo reinicio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Si, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "La cantidad máxima de mapeos de memoria es menor de lo recomendado.", + "LinuxVmMaxMapCountWarningTextSecondary": "El valor actual de vm.max_map_count ({0}) es menor que {1}. Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos actualmente. Ryujinx se bloqueará tan pronto como se supere este límite.\n\nPuede que desee aumentar manualmente el límite o instalar pkexec, lo que permite a Ryujinx ayudar con eso.", + "Settings": "Configuración", + "SettingsTabGeneral": "Interfaz de usuario", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralHideCursor": "Esconder el cursor:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", + "SettingsTabGeneralHideCursorAlways": "Siempre", + "SettingsTabGeneralGameDirectories": "Carpetas de juegos", + "SettingsTabGeneralAdd": "Agregar", + "SettingsTabGeneralRemove": "Quitar", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Núcleo", + "SettingsTabSystemSystemRegion": "Región del sistema:", + "SettingsTabSystemSystemRegionJapan": "Japón", + "SettingsTabSystemSystemRegionUSA": "EEUU", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwán", + "SettingsTabSystemSystemLanguage": "Idioma del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonés", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", + "SettingsTabSystemSystemLanguageFrench": "Francés", + "SettingsTabSystemSystemLanguageGerman": "Alemán", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Español", + "SettingsTabSystemSystemLanguageChinese": "Chino", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", + "SettingsTabSystemSystemLanguagePortuguese": "Portugués", + "SettingsTabSystemSystemLanguageRussian": "Ruso", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", + "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", + "SettingsTabSystemSystemTime": "Hora del sistema:", + "SettingsTabSystemEnableVsync": "Sincronización vertical", + "SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)", + "SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos", + "SettingsTabSystemAudioBackend": "Motor de audio:", + "SettingsTabSystemAudioBackendDummy": "Vacio", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", + "SettingsTabSystemExpandDramSize": "Usar diseño alternativo de memoria (Desarrolladores)", + "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API de gráficos", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Escala de resolución:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", + "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", + "SettingsTabLogging": "Registros", + "SettingsTabLoggingLogging": "Registros", + "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", + "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", + "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", + "SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro", + "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", + "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", + "SettingsTabLoggingDeveloperOptionsNote": "ADVERTENCIA: Reducirá el rendimiento", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nivel de registro de backend gráficos:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errores", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo", + "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", + "SettingsTabInput": "Entrada", + "SettingsTabInputEnableDockedMode": "Modo dock/TV", + "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", + "SettingsButtonSave": "Guardar", + "SettingsButtonClose": "Cerrar", + "SettingsButtonOk": "Aceptar", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jugador", + "ControllerSettingsPlayer1": "Jugador 1", + "ControllerSettingsPlayer2": "Jugador 2", + "ControllerSettingsPlayer3": "Jugador 3", + "ControllerSettingsPlayer4": "Jugador 4", + "ControllerSettingsPlayer5": "Jugador 5", + "ControllerSettingsPlayer6": "Jugador 6", + "ControllerSettingsPlayer7": "Jugador 7", + "ControllerSettingsPlayer8": "Jugador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Actualizar", + "ControllerSettingsDeviceDisabled": "Deshabilitado", + "ControllerSettingsControllerType": "Tipo de Mando", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Mando Pro", + "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Predeterminado", + "ControllerSettingsLoad": "Cargar", + "ControllerSettingsAdd": "Agregar", + "ControllerSettingsRemove": "Quitar", + "ControllerSettingsButtons": "Botones", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad direccional", + "ControllerSettingsDPadUp": "Arriba", + "ControllerSettingsDPadDown": "Abajo", + "ControllerSettingsDPadLeft": "Izquierda", + "ControllerSettingsDPadRight": "Derecha", + "ControllerSettingsStickButton": "Botón", + "ControllerSettingsStickUp": "Arriba", + "ControllerSettingsStickDown": "Abajo", + "ControllerSettingsStickLeft": "Izquierda", + "ControllerSettingsStickRight": "Derecha", + "ControllerSettingsStickStick": "Palanca", + "ControllerSettingsStickInvertXAxis": "Invertir eje X", + "ControllerSettingsStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsStickDeadzone": "Zona muerta:", + "ControllerSettingsLStick": "Palanca izquierda", + "ControllerSettingsRStick": "Palanca derecha", + "ControllerSettingsTriggersLeft": "Gatillos izquierdos", + "ControllerSettingsTriggersRight": "Gatillos derechos", + "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", + "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", + "ControllerSettingsTriggers": "Gatillos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", + "ControllerSettingsExtraButtonsRight": "Botones derechos", + "ControllerSettingsMisc": "Misceláneo", + "ControllerSettingsTriggerThreshold": "Límite de gatillos:", + "ControllerSettingsMotion": "Movimiento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", + "ControllerSettingsMotionControllerSlot": "Puerto del mando:", + "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", + "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", + "ControllerSettingsMotionServerHost": "Host del servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", + "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", + "ControllerSettingsSave": "Guardar", + "ControllerSettingsClose": "Cerrar", + "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", + "UserProfilesSaveProfileName": "Guardar nombre de perfil", + "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", + "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", + "UserProfilesAddNewProfile": "Añadir nuevo perfil", + "UserProfilesDelete": "Eliminar", + "UserProfilesClose": "Cerrar", + "ProfileNameSelectionWatermark": "Escoge un apodo", + "ProfileImageSelectionTitle": "Selección de imagen de perfil", + "ProfileImageSelectionHeader": "Elige una imagen de perfil", + "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", + "ProfileImageSelectionImportImage": "Importar imagen", + "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", + "InputDialogTitle": "Cuadro de diálogo de entrada", + "InputDialogOk": "Aceptar", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", + "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escoger", + "AvatarSetBackgroundColor": "Establecer color de fondo", + "AvatarClose": "Cerrar", + "ControllerSettingsLoadProfileToolTip": "Cargar perfil", + "ControllerSettingsAddProfileToolTip": "Agregar perfil", + "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", + "ControllerSettingsSaveProfileToolTip": "Guardar perfil", + "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", + "MenuBarFileToolsHideUi": "Ocultar interfaz", + "GameListContextMenuRunApplication": "Ejecutar aplicación", + "GameListContextMenuToggleFavorite": "Marcar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", + "ButtonBrowse": "Buscar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibración", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", + "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", + "DialogConfirmationTitle": "Ryujinx - Confirmación", + "DialogUpdaterTitle": "Ryujinx - Actualizador", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Advertencia", + "DialogExitTitle": "Ryujinx - Salir", + "DialogErrorMessage": "Ryujinx encontró un error", + "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", + "DialogExitSubMessage": "¡Se perderán los datos no guardados!", + "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", + "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", + "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", + "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", + "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", + "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Se ha producido un error al intentar obtener información de liberación de GitHub Release. Esto puede ser causado si una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.", + "DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.", + "DialogUpdaterDownloadingMessage": "Descargando actualización...", + "DialogUpdaterExtractionMessage": "Extrayendo actualización...", + "DialogUpdaterRenamingMessage": "Renombrando actualización...", + "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", + "DialogUpdaterCompleteMessage": "¡Actualización completa!", + "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", + "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", + "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", + "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", + "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", + "DialogRestartRequiredMessage": "Se necesita reiniciar", + "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", + "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", + "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", + "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", + "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", + "DialogInstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", + "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogControllerAppletTitle": "Applet de mandos", + "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", + "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", + "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", + "DialogProfileDeleteProfileTitle": "Eliminando perfil", + "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", + "DialogWarning": "Advertencia", + "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", + "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", + "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", + "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", + "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", + "DialogUserProfileUnsavedChangesTitle": "Advertencia - Cambios sin guardar", + "DialogUserProfileUnsavedChangesMessage": "Ha realizado cambios en este perfil de usuario que no han sido guardados.", + "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", + "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", + "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", + "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", + "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", + "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", + "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", + "CommonAuto": "Automático", + "CommonOff": "Desactivado", + "CommonOn": "Activado", + "InputDialogYes": "Sí", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Reanudar", + "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", + "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", + "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", + "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", + "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", + "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", + "AboutRyujinxAboutTitle": "Acerca de:", + "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", + "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", + "AmiiboSeriesLabel": "Serie de Amiibo", + "AmiiboCharacterLabel": "Personaje", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID de título", + "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", + "DlcManagerTableHeadingFullPathLabel": "Directorio completo", + "DlcManagerRemoveAllButton": "Quitar todo", + "DlcManagerEnableAllButton": "Activar todas", + "DlcManagerDisableAllButton": "Desactivar todos", + "MenuBarOptionsChangeLanguage": "Cambiar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de archivo", + "CommonSort": "Orden", + "CommonShowNames": "Mostrar nombres", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Funcionalidades Y Mejoras", + "ErrorWindowTitle": "Ventana de error", + "ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando", + "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", + "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", + "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", + "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", + "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", + "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", + "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", + "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", + "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", + "RegionTooltip": "Cambia la región del sistema", + "LanguageTooltip": "Cambia el idioma del sistema", + "TimezoneTooltip": "Cambia la zona horaria del sistema", + "TimeTooltip": "Cambia la hora del sistema", + "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", + "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", + "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", + "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", + "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", + "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", + "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", + "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", + "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", + "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", + "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", + "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", + "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", + "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", + "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", + "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", + "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", + "InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.", + "WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.", + "ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.", + "TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.", + "GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.", + "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", + "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", + "DeveloperOptionTooltip": "Usar con cuidado", + "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", + "DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", + "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", + "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", + "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", + "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", + "ExitTooltip": "Cierra Ryujinx", + "OpenSettingsTooltip": "Abre la ventana de configuración", + "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", + "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", + "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", + "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", + "GridSize": "Tamaño de cuadrícula", + "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", + "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", + "SettingsTabSystemAudioVolume": "Volumen: ", + "AudioVolumeTooltip": "Ajusta el nivel de volumen", + "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", + "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", + "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", + "GameListContextMenuManageCheat": "Administrar cheats", + "ControllerSettingsStickRange": "Alcance:", + "DialogStopEmulationTitle": "Ryujinx - Detener emulación", + "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Sonido", + "SettingsTabNetwork": "Red", + "SettingsTabNetworkConnection": "Conexión de red", + "SettingsTabCpuCache": "Caché de CPU", + "SettingsTabCpuMemory": "Memoria de CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", + "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", + "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", + "IconSize": "Tamaño de iconos", + "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", + "MenuBarOptionsShowConsole": "Mostrar consola", + "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", + "UserErrorNoKeys": "No se encontraron keys", + "UserErrorNoFirmware": "No se encontró firmware", + "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", + "UserErrorApplicationNotFound": "No se encontró la aplicación", + "UserErrorUnknown": "Error desconocido", + "UserErrorUndefined": "Error indefinido", + "UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.", + "UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.", + "UserErrorUnknownDescription": "¡Ocurrió un error desconocido!", + "UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!", + "OpenSetupGuideMessage": "Abrir la guía de instalación", + "NoUpdate": "No actualizado", + "TitleUpdateVersionLabel": "Versión {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmación", + "FileDialogAllTypes": "Todos los tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", + "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado de software", + "SoftwareKeyboardModeNumbersOnly": "Solo deben ser números", + "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", + "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", + "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", + "UpdaterRenaming": "Renombrando archivos viejos...", + "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", + "UpdaterAddingFiles": "Añadiendo nuevos archivos...", + "UpdaterExtracting": "Extrayendo actualización...", + "UpdaterDownloading": "Descargando actualización...", + "Game": "Juego", + "Docked": "Dock/TV", + "Handheld": "Portátil", + "ConnectionError": "Error de conexión.", + "AboutPageDeveloperListMore": "{0} y más...", + "ApiError": "Error de API.", + "LoadingHeading": "Cargando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando sombreadores", + "AllKeyboards": "Todos los teclados", + "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", + "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", + "AllSupportedFormats": "Todos los formatos soportados", + "RyujinxUpdater": "Actualizador de Ryujinx", + "SettingsTabHotkeys": "Atajos de teclado", + "SettingsTabHotkeysHotkeys": "Atajos de teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:", + "SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Silenciar:", + "ControllerMotionTitle": "Opciones de controles de movimiento", + "ControllerRumbleTitle": "Opciones de vibración", + "SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema", + "SettingsXamlThemeFile": "Archivo de tema Xaml", + "AvatarWindowTitle": "Administrar cuentas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconocido", + "Usage": "Uso", + "Writable": "Escribible", + "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", + "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "UserProfileWindowTitle": "Administrar perfiles de usuario", + "CheatWindowTitle": "Administrar cheats", + "DlcWindowTitle": "Administrar contenido descargable", + "UpdateWindowTitle": "Administrar actualizaciones", + "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", + "BuildId": "Id de compilación:", + "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "UserProfilesEditProfile": "Editar selección", + "Cancel": "Cancelar", + "Save": "Guardar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", + "UserProfileEmptyNameError": "El nombre es obligatorio", + "UserProfileNoImageError": "Debe establecerse la imagen de perfil", + "GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:", + "SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:", + "UserProfilesName": "Nombre:", + "UserProfilesUserId": "Id de Usuario:", + "SettingsTabGraphicsBackend": "Fondo de gráficos", + "SettingsTabGraphicsBackendTooltip": "Back-end de los gráficos a utilizar", + "SettingsEnableTextureRecompression": "Activar recompresión de texturas", + "SettingsEnableTextureRecompressionTooltip": "Comprime ciertas texturas para reducir el uso de la VRAM.\n\nRecomendado para GPUs que tienen menos de 4 GB de VRAM.\n\nMantén esta opción desactivada si no estás seguro.", + "SettingsTabGraphicsPreferredGpu": "GPU preferida", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", + "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", + "SettingsGpuBackendRestartMessage": "La configuración de la GPU o del back-end de los gráficos fue modificada. Es necesario reiniciar para que se aplique.", + "SettingsGpuBackendRestartSubMessage": "¿Quieres reiniciar ahora?", + "RyujinxUpdaterMessage": "¿Quieres actualizar Ryujinx a la última versión?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volumen:", + "SettingsTabHotkeysVolumeDownHotkey": "Disminuir volumen:", + "SettingsEnableMacroHLE": "Activar Macros HLE", + "SettingsEnableMacroHLETooltip": "Emulación alto-nivel del código de Macros de GPU\n\nIncrementa el rendimiento, pero puede causar errores gráficos en algunos juegos.\n\nDeja esta opción activada si no estás seguro.", + "SettingsEnableColorSpacePassthrough": "Paso de espacio de color", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige el backend de Vulkan a pasar a través de la información del color sin especificar un espacio de color. Para los usuarios con pantallas de gran gama, esto puede resultar en colores más vibrantes, a costa de la corrección del color.", + "VolumeShort": "Volumen", + "UserProfilesManageSaves": "Administrar mis partidas guardadas", + "DeleteUserSave": "¿Quieres borrar los datos de usuario de este juego?", + "IrreversibleActionNote": "Esta acción no es reversible.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Administrador de datos de guardado.", + "Name": "Nombre", + "Size": "Tamaño", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar cuentas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", + "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", + "GraphicsAATooltip": "Aplica el suavizado de bordes al procesamiento del juego", + "GraphicsAALabel": "Suavizado de bordes:", + "GraphicsScalingFilterLabel": "Filtro de escalado:", + "GraphicsScalingFilterTooltip": "Activa el escalado de búfer de fotogramas", + "GraphicsScalingFilterLevelLabel": "Nivel", + "GraphicsScalingFilterLevelTooltip": "Establecer nivel del filtro de escalado", + "SmaaLow": "SMAA Bajo", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuario", + "UserEditorTitleCreate": "Crear Usuario", + "SettingsTabNetworkInterface": "Interfaz de Red", + "NetworkInterfaceTooltip": "Interfaz de red usada para las características LAN", + "NetworkInterfaceDefault": "Predeterminado", + "PackagingShaders": "Empaquetando sombreadores", + "AboutChangelogButton": "Ver registro de cambios en GitHub", + "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json new file mode 100644 index 00000000..5bab6f7b --- /dev/null +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -0,0 +1,656 @@ +{ + "Language": "Français", + "MenuBarFileOpenApplet": "Ouvrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Éditeur Applet Mii en mode autonome", + "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", + "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", + "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", + "SettingsTabSystemMemoryManagerModeHost": "Hôte (rapide)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hôte non vérifié (plus rapide, non sécurisé)", + "SettingsTabSystemUseHypervisor": "Utiliser l'Hyperviseur", + "MenuBarFile": "_Fichier", + "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", + "MenuBarFileOpenUnpacked": "Charger un jeu extrait", + "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", + "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", + "MenuBarFileExit": "_Quitter", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer jeux en plein écran", + "MenuBarOptionsStopEmulation": "Arrêter l'émulation", + "MenuBarOptionsSettings": "_Paramètres", + "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simuler un message de réveil", + "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarTools": "_Outils", + "MenuBarToolsInstallFirmware": "Installer un firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", + "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", + "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", + "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", + "MenuBarHelp": "Aide", + "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", + "MenuBarHelpAbout": "Á propos", + "MenuSearch": "Rechercher...", + "GameListHeaderFavorite": "Favoris", + "GameListHeaderIcon": "Icône", + "GameListHeaderApplication": "Nom", + "GameListHeaderDeveloper": "Développeur", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Temps de jeu", + "GameListHeaderLastPlayed": "jouer la dernière fois", + "GameListHeaderFileExtension": "Fichier externe", + "GameListHeaderFileSize": "Taille du Fichier", + "GameListHeaderPath": "Chemin", + "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", + "GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", + "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", + "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion de la mise à jour des titres", + "GameListContextMenuManageDlc": "Gérer les DLC", + "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", + "GameListContextMenuCacheManagement": "Gestion des caches", + "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", + "GameListContextMenuExtractData": "Extraire les données", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", + "StatusBarSystemVersion": "Version du Firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappages de mémoire détectés", + "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappages de mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", + "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", + "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx s'écrasera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", + "Settings": "Paramètres", + "SettingsTabGeneral": "Interface Utilisateur", + "SettingsTabGeneralGeneral": "Général", + "SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", + "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"", + "SettingsTabGeneralHideCursor": "Masquer le Curseur :", + "SettingsTabGeneralHideCursorNever": "Jamais", + "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", + "SettingsTabGeneralHideCursorAlways": "Toujours", + "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralAdd": "Ajouter", + "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabSystem": "Système", + "SettingsTabSystemCore": "Cœur", + "SettingsTabSystemSystemRegion": "Région du système:", + "SettingsTabSystemSystemRegionJapan": "Japon", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australie", + "SettingsTabSystemSystemRegionChina": "Chine", + "SettingsTabSystemSystemRegionKorea": "Corée", + "SettingsTabSystemSystemRegionTaiwan": "Taïwan", + "SettingsTabSystemSystemLanguage": "Langue du système:", + "SettingsTabSystemSystemLanguageJapanese": "Japonais", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Anglais Américain", + "SettingsTabSystemSystemLanguageFrench": "Français", + "SettingsTabSystemSystemLanguageGerman": "Allemand", + "SettingsTabSystemSystemLanguageItalian": "Italien", + "SettingsTabSystemSystemLanguageSpanish": "Espagnol", + "SettingsTabSystemSystemLanguageChinese": "Chinois", + "SettingsTabSystemSystemLanguageKorean": "Coréen", + "SettingsTabSystemSystemLanguageDutch": "Néerlandais", + "SettingsTabSystemSystemLanguagePortuguese": "Portugais", + "SettingsTabSystemSystemLanguageRussian": "Russe", + "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", + "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais britannique ", + "SettingsTabSystemSystemLanguageCanadianFrench": "Français Canadien", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", + "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", + "SettingsTabSystemSystemTime": "Heure du système:", + "SettingsTabSystemEnableVsync": "Activer le VSync", + "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", + "SettingsTabSystemAudioBackend": "Back-end audio", + "SettingsTabSystemAudioBackendDummy": "Factice", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", + "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", + "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", + "SettingsTabGraphics": "Graphique", + "SettingsTabGraphicsAPI": "API Graphique", + "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", + "SettingsTabGraphicsResolutionScaleCustom": "Personnalisée (Non recommandée)", + "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Format :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", + "SettingsTabGraphicsDeveloperOptions": "Options développeur", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabLogging": "Journaux", + "SettingsTabLoggingLogging": "Journaux", + "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", + "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", + "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", + "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", + "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", + "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", + "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", + "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout", + "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", + "SettingsTabInput": "Contrôles", + "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", + "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", + "SettingsButtonSave": "Enregistrer", + "SettingsButtonClose": "Fermer", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Annuler", + "SettingsButtonApply": "Appliquer", + "ControllerSettingsPlayer": "Joueur", + "ControllerSettingsPlayer1": "Joueur 1", + "ControllerSettingsPlayer2": "Joueur 2", + "ControllerSettingsPlayer3": "Joueur 3", + "ControllerSettingsPlayer4": "Joueur 4", + "ControllerSettingsPlayer5": "Joueur 5", + "ControllerSettingsPlayer6": "Joueur 6", + "ControllerSettingsPlayer7": "Joueur 7", + "ControllerSettingsPlayer8": "Joueur 8", + "ControllerSettingsHandheld": "Portable", + "ControllerSettingsInputDevice": "Périphériques", + "ControllerSettingsRefresh": "Actualiser", + "ControllerSettingsDeviceDisabled": "Désactivé", + "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerTypeHandheld": "Portable", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Défaut", + "ControllerSettingsLoad": "Charger", + "ControllerSettingsAdd": "Ajouter", + "ControllerSettingsRemove": "Supprimer", + "ControllerSettingsButtons": "Boutons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPadUp": "Haut", + "ControllerSettingsDPadDown": "Bas", + "ControllerSettingsDPadLeft": "Gauche", + "ControllerSettingsDPadRight": "Droite", + "ControllerSettingsStickButton": "Bouton", + "ControllerSettingsStickUp": "Haut", + "ControllerSettingsStickDown": "Bas", + "ControllerSettingsStickLeft": "Gauche", + "ControllerSettingsStickRight": "Droite", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsStickDeadzone": "Zone morte :", + "ControllerSettingsLStick": "Joystick Gauche", + "ControllerSettingsRStick": "Joystick Droit", + "ControllerSettingsTriggersLeft": "Gachettes Gauche", + "ControllerSettingsTriggersRight": "Gachettes Droite", + "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", + "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", + "ControllerSettingsTriggers": "Gachettes", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", + "ControllerSettingsExtraButtonsRight": "Boutons Droite", + "ControllerSettingsMisc": "Divers", + "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", + "ControllerSettingsMotion": "Mouvements", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", + "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", + "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", + "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", + "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", + "ControllerSettingsSave": "Enregistrer", + "ControllerSettingsClose": "Fermer", + "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSaveProfileName": "Enregistrer le nom du profil", + "UserProfilesChangeProfileImage": "Changer l'image du profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", + "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesDelete": "Supprimer", + "UserProfilesClose": "Fermer", + "ProfileNameSelectionWatermark": "Choisir un pseudo", + "ProfileImageSelectionTitle": "Sélection de l'image du profil", + "ProfileImageSelectionHeader": "Choisir l'image du profil", + "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", + "ProfileImageSelectionImportImage": "Importer une image", + "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", + "InputDialogTitle": "Fenêtre d'entrée de texte", + "InputDialogOk": "OK", + "InputDialogCancel": "Annuler", + "InputDialogAddNewProfileTitle": "Choisir un nom de profil", + "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", + "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", + "AvatarChoose": "Choisir", + "AvatarSetBackgroundColor": "Choisir une couleur de fond", + "AvatarClose": "Fermer", + "ControllerSettingsLoadProfileToolTip": "Charger un profil", + "ControllerSettingsAddProfileToolTip": "Ajouter un profil", + "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", + "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", + "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", + "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", + "GameListContextMenuRunApplication": "Démarrer l'application", + "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", + "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", + "SettingsTabGeneralTheme": "Thème", + "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", + "SettingsTabGeneralThemeBaseStyle": "Style par défaut", + "SettingsTabGeneralThemeBaseStyleDark": "Sombre", + "SettingsTabGeneralThemeBaseStyleLight": "Lumière", + "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", + "ButtonBrowse": "Parcourir", + "ControllerSettingsConfigureGeneral": "Configurer", + "ControllerSettingsRumble": "Vibreur", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicateur de vibrations faibles", + "DialogMessageSaveNotAvailableMessage": "Il n'y a aucune sauvegarde pour {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Voulez-vous créer une sauvegarde pour ce jeu ?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Mise à Jour", + "DialogErrorTitle": "Ryujinx - Erreur", + "DialogWarningTitle": "Ryujinx - Avertissement", + "DialogExitTitle": "Ryujinx - Quitter", + "DialogErrorMessage": "Ryujinx a rencontré une erreur", + "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", + "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", + "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", + "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", + "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", + "DialogNcaExtractionMessage": "Extraction de la section {0} depuis {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extracteur de la section NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Échec de l'extraction. Le NCA principal n'était pas présent dans le fichier sélectionné.", + "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", + "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", + "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", + "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", + "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", + "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", + "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", + "DialogUpdaterCompleteMessage": "Mise à jour terminée !", + "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", + "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", + "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", + "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", + "DialogRestartRequiredMessage": "Redémarrage Requis", + "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", + "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", + "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", + "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", + "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", + "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", + "DialogInstallFileTypesErrorMessage": "Échec de l'installation des types de fichiers.", + "DialogUninstallFileTypesSuccessMessage": "Types de fichiers désinstallés avec succès!", + "DialogUninstallFileTypesErrorMessage": "Échec de la désinstallation des types de fichiers.", + "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPour plus d'informations sur la manière de corriger cette erreur, suivez notre Guide d'Installation.", + "DialogUserErrorDialogTitle": "Erreur Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Une erreur est survenue lors de la récupération des informations de l'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.", + "DialogProfileInvalidProfileErrorMessage": "Le profil {0} est incompatible avec le système de configuration de manette actuel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Le profil par défaut ne peut pas être écrasé", + "DialogProfileDeleteProfileTitle": "Supprimer le profil", + "DialogProfileDeleteProfileMessage": "Cette action est irréversible, êtes-vous sûr de vouloir continuer ?", + "DialogWarning": "Avertissement", + "DialogPPTCDeletionMessage": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogPPTCDeletionErrorMessage": "Erreur lors de la purge du cache PPTC à {0}: {1}", + "DialogShaderDeletionMessage": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogShaderDeletionErrorMessage": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx a rencontré une erreur", + "DialogInvalidTitleIdErrorMessage": "Erreur d'UI : le jeu sélectionné n'a pas d'ID de titre valide", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware valide n'a pas été trouvé dans {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installer le Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La version {0} du système sera installée.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nCela remplacera la version actuelle du système {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVoulez-vous continuer ?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installation du firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Version du système {0} installée avec succès.", + "DialogUserProfileDeletionWarningMessage": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé", + "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", + "DialogUserProfileUnsavedChangesTitle": "Avertissement - Modifications non enregistrées", + "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", + "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", + "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", + "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", + "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", + "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Vous avez activé l'extraction des shaders, qui est conçu pour être utilisé par les développeurs uniquement.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver l'extraction des shaders. Souhaitez-vous désactiver l'extraction des shaders maintenant ?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un jeu a déjà été chargé", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Veuillez arrêter l'émulation ou fermer l'émulateur avant de lancer un autre jeu.", + "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", + "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", + "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", + "CommonAuto": "Auto", + "CommonOff": "Désactivé", + "CommonOn": "Activé", + "InputDialogYes": "Oui", + "InputDialogNo": "Non", + "DialogProfileInvalidProfileNameErrorMessage": "Le nom du fichier contient des caractères invalides. Veuillez réessayer.", + "MenuBarOptionsPauseEmulation": "Suspendre", + "MenuBarOptionsResumeEmulation": "Reprendre", + "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", + "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", + "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", + "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", + "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", + "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", + "AboutRyujinxAboutTitle": "Á propos:", + "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", + "AboutRyujinxMaintainersTitle": "Maintenu par :", + "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", + "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", + "AmiiboSeriesLabel": "Séries Amiibo", + "AmiiboCharacterLabel": "Personnage", + "AmiiboScanButtonLabel": "Scanner", + "AmiiboOptionsShowAllLabel": "Afficher tous les Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack : Utiliser un tag Uuid aléatoire", + "DlcManagerTableHeadingEnabledLabel": "Activé", + "DlcManagerTableHeadingTitleIdLabel": "ID du titre", + "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", + "DlcManagerTableHeadingFullPathLabel": "Chemin complet", + "DlcManagerRemoveAllButton": "Tout supprimer", + "DlcManagerEnableAllButton": "Activer Tout", + "DlcManagerDisableAllButton": "Désactiver Tout", + "MenuBarOptionsChangeLanguage": "Changer la Langue", + "MenuBarShowFileTypes": "Afficher les types de fichiers", + "CommonSort": "Trier", + "CommonShowNames": "Afficher les noms", + "CommonFavorite": "Favoris", + "OrderAscending": "Croissant", + "OrderDescending": "Décroissant", + "SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations", + "ErrorWindowTitle": "Fenêtre d'erreur", + "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", + "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", + "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", + "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", + "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", + "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", + "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", + "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Fournit aux jeux l'accès à votre clavier en tant que périphérique de saisie de texte.", + "DirectMouseTooltip": "Prise en charge de l'accès à la souris (HID). Permet aux jeux d'accéder a votre souris en tant que périphérique de pointage.", + "RegionTooltip": "Changer la région du système", + "LanguageTooltip": "Changer la langue du système", + "TimezoneTooltip": "Changer le fuseau horaire du système", + "TimeTooltip": "Changer l'heure du système", + "VSyncToggleTooltip": "Synchronisation verticale de l'émulateur. Généralement un limiteur d'FPS pour la majorité des jeux, le désactiver peut accélérer les jeux pour rendre les écrans de chargement plus court, mais augemente le risque de crash lors des chargements.\n\nPeut être activer ou desactiver en jeu avec un raccourci de votre choix. Nous vous recommandons de le laisser.\n\nLaissez activer, si vous n'êtes pas sûr.", + "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", + "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", + "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", + "MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.", + "MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.", + "MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.", + "MemoryManagerUnsafeTooltip": "Mapper directement la mémoire dans la carte, mais ne pas masquer l'adresse dans l'espace d'adressage du client avant l'accès. Plus rapide, mais la sécurité sera négliger. L'application peut accéder à la mémoire depuis n'importe où dans Ryujinx, donc exécutez uniquement les programmes en qui vous avez confiance avec ce mode.", + "UseHypervisorTooltip": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsqu'il est disponible, mais peut être instable dans son état actuel.", + "DRamTooltip": "Utilise une disposition alternative de la mémoire pour imiter le kit de développeur de la Switch.\n\nActiver cette option uniquement pour les packs de textures 4k ou les mods à résolution 4k.\nN'améliore pas les performances, cause des crashs dans certains jeux si activer.\n\nLaissez Désactiver en cas d'incertitude.", + "IgnoreMissingServicesTooltip": "Ignore les services Horizon OS non-intégré. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nActivez-le en cas d'incertitude.", + "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", + "ResolutionScaleTooltip": "Échelle de résolution appliquer au rendu du jeu", + "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", + "AnisotropyTooltip": "Niveau de Filtrage Anisotropique (mettre sur Auto pour utiliser la valeur demandée par le jeu)", + "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", + "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", + "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", + "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", + "InfoLogTooltip": "Affiche les messages de log d'informations dans la console. N'affecte pas les performances.", + "WarnLogTooltip": "Affiche les messages d'avertissement dans la console. N'affecte pas les performances.", + "ErrorLogTooltip": "Affiche les messages de log d'erreur dans la console. N'affecte pas les performances.", + "TraceLogTooltip": "Affiche la trace des messages de log dans la console. N'affecte pas les performances.", + "GuestLogTooltip": "Affiche les messages de log des invités dans la console. N'affecte pas les performances.", + "FileAccessLogTooltip": "Affiche les messages de log d'accès aux fichiers dans la console.", + "FSAccessLogModeTooltip": "Active la sortie du journal d'accès FS de la console. Les modes possibles sont 0-3", + "DeveloperOptionTooltip": "Utiliser avec précaution", + "OpenGlLogLevel": "Nécessite l'activation des niveaux de journalisation appropriés", + "DebugLogTooltip": "Affiche les messages de débogage dans la console.\n\nN'utilisez ceci que si un membre du personnel le demande, car cela rendra les logs difficiles à lire et réduit les performances de l'émulateur.", + "LoadApplicationFileTooltip": "Ouvrir un explorateur de fichiers pour choisir un fichier compatible Switch à charger", + "LoadApplicationFolderTooltip": "Ouvrir un explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger", + "OpenRyujinxFolderTooltip": "Ouvrir le dossier du système de fichiers Ryujinx", + "OpenRyujinxLogsTooltip": "Ouvre le dossier dans lequel les journaux sont écrits", + "ExitTooltip": "Quitter Ryujinx", + "OpenSettingsTooltip": "Ouvrir la fenêtre de configuration", + "OpenProfileManagerTooltip": "Ouvrir la fenêtre du gestionnaire de profils d'utilisateurs", + "StopEmulationTooltip": "Arrêter l'émulation du jeu en cours et revenir à la sélection des jeux", + "CheckUpdatesTooltip": "Vérifier les mises à jour de Ryujinx", + "OpenAboutTooltip": "Ouvrir la fenêtre À Propos", + "GridSize": "Taille de la grille", + "GridSizeTooltip": "Modifier la taille des éléments de la grille", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugais brésilien", + "AboutRyujinxContributorsButtonHeader": "Voir tous les contributeurs", + "SettingsTabSystemAudioVolume": "Volume :", + "AudioVolumeTooltip": "Modifier le volume audio", + "SettingsTabSystemEnableInternetAccess": "Accès Internet Invité/Mode LAN", + "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "GameListContextMenuManageCheatToolTip": "Gérer la triche", + "GameListContextMenuManageCheat": "Gérer la triche", + "ControllerSettingsStickRange": "Intervalle:", + "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", + "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Réseau", + "SettingsTabNetworkConnection": "Connexion réseau", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Mémoire CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", + "ControllerSettingsRotate90": "Rotation 90° horaire", + "IconSize": "Taille d'icône", + "IconSizeTooltip": "Changer la taille des icônes de jeu", + "MenuBarOptionsShowConsole": "Afficher la console", + "ShaderCachePurgeError": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "UserErrorNoKeys": "Clés introuvables", + "UserErrorNoFirmware": "Firmware introuvable", + "UserErrorFirmwareParsingFailed": "Erreur d'analyse du firmware", + "UserErrorApplicationNotFound": " Application introuvable", + "UserErrorUnknown": "Erreur inconnue", + "UserErrorUndefined": "Erreur non définie", + "UserErrorNoKeysDescription": "Ryujinx n'a pas pu trouver votre fichier 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", + "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", + "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", + "OpenSetupGuideMessage": "Ouvrir le guide d'installation", + "NoUpdate": "Aucune mise à jour", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "Tous les types", + "Never": "Jamais", + "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", + "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", + "SoftwareKeyboard": "Clavier logiciel", + "SoftwareKeyboardModeNumbersOnly": "Doit être uniquement des chiffres", + "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", + "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", + "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", + "UpdaterRenaming": "Renommage des anciens fichiers...", + "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", + "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", + "UpdaterExtracting": "Extraction de la mise à jour…", + "UpdaterDownloading": "Téléchargement de la mise à jour...", + "Game": "Jeu", + "Docked": "Attaché", + "Handheld": "Portable", + "ConnectionError": "Erreur de connexion.", + "AboutPageDeveloperListMore": "{0} et plus...", + "ApiError": "Erreur API.", + "LoadingHeading": "Chargement {0}", + "CompilingPPTC": "Compilation PTC", + "CompilingShaders": "Compilation des Shaders", + "AllKeyboards": "Tous les claviers", + "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", + "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", + "AllSupportedFormats": "Tous les formats supportés", + "RyujinxUpdater": "Mise à jour de Ryujinx", + "SettingsTabHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", + "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", + "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", + "SettingsTabHotkeysPauseHotkey": "Suspendre :", + "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", + "ControllerMotionTitle": "Réglages du contrôle par mouvement", + "ControllerRumbleTitle": "Paramètres de Vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", + "SettingsXamlThemeFile": "Fichier thème Xaml", + "AvatarWindowTitle": "Gérer les Comptes - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Inconnu", + "Usage": "Utilisation", + "Writable": "Ecriture possible", + "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", + "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", + "CheatWindowTitle": "Gestionnaire de triches", + "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", + "UpdateWindowTitle": "Gestionnaire de mises à jour", + "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", + "UserProfilesEditProfile": "Éditer la sélection", + "Cancel": "Annuler", + "Save": "Enregistrer", + "Discard": "Abandonner", + "UserProfilesSetProfileImage": "Modifier l'image du profil", + "UserProfileEmptyNameError": "Le nom est requis", + "UserProfileNoImageError": "L'image du profil doit être définie", + "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", + "UserProfilesName": "Nom :", + "UserProfilesUserId": "Identifiant de l'utilisateur :", + "SettingsTabGraphicsBackend": "API de Rendu", + "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", + "SettingsEnableTextureRecompression": "Activer la recompression des textures", + "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsTabGraphicsPreferredGpu": "GPU préféré", + "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", + "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", + "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", + "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", + "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", + "SettingsEnableMacroHLE": "Activer les macros HLE", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans haut de gamme, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gérer les sauvegardes", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", + "IrreversibleActionNote": "Cette action n'est pas réversible.", + "SaveManagerHeading": "Gérer les sauvegardes pour {0}", + "SaveManagerTitle": "Gestionnaire de sauvegarde", + "Name": "Nom ", + "Size": "Taille", + "Search": "Rechercher", + "UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus", + "Recover": "Récupérer", + "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", + "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", + "GraphicsAATooltip": "Applique l'anticrénelage au rendu du jeu", + "GraphicsAALabel": "Anticrénelage :", + "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", + "GraphicsScalingFilterTooltip": "Active la mise à l'échelle du tampon d'image", + "GraphicsScalingFilterLevelLabel": "Niveau ", + "GraphicsScalingFilterLevelTooltip": "Définir le niveau du filtre de mise à l'échelle", + "SmaaLow": "SMAA Faible", + "SmaaMedium": "SMAA moyen", + "SmaaHigh": "SMAA Élevé", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modifier Utilisateur", + "UserEditorTitleCreate": "Créer Utilisateur", + "SettingsTabNetworkInterface": "Interface Réseau :", + "NetworkInterfaceTooltip": "Interface réseau pour les fonctionnalités LAN", + "NetworkInterfaceDefault": "Par défaut", + "PackagingShaders": "Empaquetage des Shaders", + "AboutChangelogButton": "Voir le Changelog sur GitHub", + "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json new file mode 100644 index 00000000..e5caf445 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -0,0 +1,656 @@ +{ + "Language": "אנגלית (ארה\"ב)", + "MenuBarFileOpenApplet": "פתח יישומון", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", + "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", + "SettingsTabSystemMemoryManagerMode": "מצב מנהל זיכרון:", + "SettingsTabSystemMemoryManagerModeSoftware": "תוכנה", + "SettingsTabSystemMemoryManagerModeHost": "מארח (מהיר)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "מארח לא מבוקר (המהיר ביותר, לא בטוח)", + "SettingsTabSystemUseHypervisor": "השתמש ב Hypervisor", + "MenuBarFile": "_קובץ", + "MenuBarFileOpenFromFile": "_טען יישום מקובץ", + "MenuBarFileOpenUnpacked": "טען משחק _שאינו ארוז", + "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", + "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", + "MenuBarFileExit": "_יציאה", + "MenuBarOptions": "הגדרות", + "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", + "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", + "MenuBarOptionsStopEmulation": "עצור אמולציה", + "MenuBarOptionsSettings": "_הגדרות", + "MenuBarOptionsManageUserProfiles": "_נהל פרופילי משתמש", + "MenuBarActions": "_פעולות", + "MenuBarOptionsSimulateWakeUpMessage": "דמה הודעת השכמה", + "MenuBarActionsScanAmiibo": "סרוק אמיבו", + "MenuBarTools": "_כלים", + "MenuBarToolsInstallFirmware": "התקן קושחה", + "MenuBarFileToolsInstallFirmwareFromFile": "התקן קושחה מקובץ- ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "התקן קושחה מתוך תקייה", + "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", + "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", + "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", + "MenuBarHelp": "עזרה", + "MenuBarHelpCheckForUpdates": "חפש עדכונים", + "MenuBarHelpAbout": "אודות", + "MenuSearch": "חפש...", + "GameListHeaderFavorite": "אהוב", + "GameListHeaderIcon": "סמל", + "GameListHeaderApplication": "שם", + "GameListHeaderDeveloper": "מפתח", + "GameListHeaderVersion": "גרסה", + "GameListHeaderTimePlayed": "זמן משחק", + "GameListHeaderLastPlayed": "שוחק לאחרונה", + "GameListHeaderFileExtension": "סיומת קובץ", + "GameListHeaderFileSize": "גודל הקובץ", + "GameListHeaderPath": "נתיב", + "GameListContextMenuOpenUserSaveDirectory": "פתח את תקיית השמור של המשתמש", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "פותח את תקיית השמור של המשתמש ביישום הנוכחי", + "GameListContextMenuOpenDeviceSaveDirectory": "פתח את תקיית השמור של המכשיר", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "פותח את הספרייה המכילה את שמור המכשיר של היישום", + "GameListContextMenuOpenBcatSaveDirectory": "פתח את תקיית השמור של ה-BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "פותח את תקיית שמור ה-BCAT של היישום", + "GameListContextMenuManageTitleUpdates": "מנהל עדכוני משחקים", + "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", + "GameListContextMenuManageDlc": "מנהל הרחבות", + "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", + "GameListContextMenuOpenModsDirectory": "פתח את תקיית המודים", + "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התקייה המכילה את המודים של היישום", + "GameListContextMenuCacheManagement": "ניהול מטמון", + "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", + "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", + "GameListContextMenuCacheManagementPurgeShaderCache": "ניקוי מטמון הצללות", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "מוחק את מטמון ההצללות של היישום", + "GameListContextMenuCacheManagementOpenPptcDirectory": "פתח את תקיית PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "פותח את התקייה של מטמון ה-PPTC של האפליקציה", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "פתח את תקיית המטמון של ההצללות", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "פותח את תקיית מטמון ההצללות של היישום", + "GameListContextMenuExtractData": "חילוץ נתונים", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "חלץ את קטע ה-ExeFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", + "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", + "StatusBarSystemVersion": "גרסת מערכת: {0}", + "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", + "LinuxVmMaxMapCountDialogTextPrimary": "האם תרצה להגביר את הערך של vm.max_map_count ל{0}", + "LinuxVmMaxMapCountDialogTextSecondary": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "כן, עד האתחול הבא", + "LinuxVmMaxMapCountDialogButtonPersistent": "כן, לצמיתות", + "LinuxVmMaxMapCountWarningTextPrimary": "הכמות המירבית של מיפויי הזיכרון נמוכה מהמומלץ.", + "LinuxVmMaxMapCountWarningTextSecondary": "הערך הנוכחי של vm.max_map_count {0} נמוך מ{1}. משחקים מסוימים עלולים לייצר עוד מיפוי זיכרון ממה שמתאפשר.Ryujinx יקרוס ברגע שהמגבלה תחרוג.\n\nיתכן ותרצה להעלות את המגבלה הנוכחית או להתקין את pkexec, אשר יאפשר לRyujinx לסייע בכך.", + "Settings": "הגדרות", + "SettingsTabGeneral": "ממשק משתמש", + "SettingsTabGeneralGeneral": "כללי", + "SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד", + "SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה", + "SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"", + "SettingsTabGeneralHideCursor": "הסתר את הסמן", + "SettingsTabGeneralHideCursorNever": "אף פעם", + "SettingsTabGeneralHideCursorOnIdle": "במצב סרק", + "SettingsTabGeneralHideCursorAlways": "תמיד", + "SettingsTabGeneralGameDirectories": "תקיות משחקים", + "SettingsTabGeneralAdd": "הוסף", + "SettingsTabGeneralRemove": "הסר", + "SettingsTabSystem": "מערכת", + "SettingsTabSystemCore": "ליבה", + "SettingsTabSystemSystemRegion": "אזור מערכת:", + "SettingsTabSystemSystemRegionJapan": "יפן", + "SettingsTabSystemSystemRegionUSA": "ארה\"ב", + "SettingsTabSystemSystemRegionEurope": "אירופה", + "SettingsTabSystemSystemRegionAustralia": "אוסטרליה", + "SettingsTabSystemSystemRegionChina": "סין", + "SettingsTabSystemSystemRegionKorea": "קוריאה", + "SettingsTabSystemSystemRegionTaiwan": "טייוואן", + "SettingsTabSystemSystemLanguage": "שפת המערכת:", + "SettingsTabSystemSystemLanguageJapanese": "יפנית", + "SettingsTabSystemSystemLanguageAmericanEnglish": "אנגלית אמריקאית", + "SettingsTabSystemSystemLanguageFrench": "צרפתית", + "SettingsTabSystemSystemLanguageGerman": "גרמנית", + "SettingsTabSystemSystemLanguageItalian": "איטלקית", + "SettingsTabSystemSystemLanguageSpanish": "ספרדית", + "SettingsTabSystemSystemLanguageChinese": "סינית", + "SettingsTabSystemSystemLanguageKorean": "קוריאנית", + "SettingsTabSystemSystemLanguageDutch": "הולנדית", + "SettingsTabSystemSystemLanguagePortuguese": "פורטוגזית", + "SettingsTabSystemSystemLanguageRussian": "רוסית", + "SettingsTabSystemSystemLanguageTaiwanese": "טייוואנית", + "SettingsTabSystemSystemLanguageBritishEnglish": "אנגלית בריטית", + "SettingsTabSystemSystemLanguageCanadianFrench": "צרפתית קנדית", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "ספרדית אמריקה הלטינית", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "סינית פשוטה", + "SettingsTabSystemSystemLanguageTraditionalChinese": "סינית מסורתית", + "SettingsTabSystemSystemTimeZone": "אזור זמן מערכת:", + "SettingsTabSystemSystemTime": "זמן מערכת:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS בדיקות תקינות", + "SettingsTabSystemAudioBackend": "אחראי שמע:", + "SettingsTabSystemAudioBackendDummy": "גולם", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "האצות", + "SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות", + "SettingsTabSystemExpandDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)", + "SettingsTabSystemIgnoreMissingServices": "התעלם משירותים חסרים", + "SettingsTabGraphics": "גרפיקה", + "SettingsTabGraphicsAPI": "ממשק גראפי", + "SettingsTabGraphicsEnableShaderCache": "הפעל מטמון הצללות", + "SettingsTabGraphicsAnisotropicFiltering": "סינון אניסוטרופי:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "אוטומטי", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "קנה מידה של רזולוציה:", + "SettingsTabGraphicsResolutionScaleCustom": "מותאם אישית (לא מומלץ)", + "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "מתח לגודל חלון", + "SettingsTabGraphicsDeveloperOptions": "אפשרויות מפתח", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "רישום", + "SettingsTabLoggingLogging": "רישום", + "SettingsTabLoggingEnableLoggingToFile": "אפשר רישום לקובץ", + "SettingsTabLoggingEnableStubLogs": "אפשר רישום בדל", + "SettingsTabLoggingEnableInfoLogs": "אפשר רישום מידע", + "SettingsTabLoggingEnableWarningLogs": "אפשר רישום אזהרות", + "SettingsTabLoggingEnableErrorLogs": "אפשר רישום שגיאות", + "SettingsTabLoggingEnableTraceLogs": "הפעל רישום מעקבי", + "SettingsTabLoggingEnableGuestLogs": "הפעל רישום מארח", + "SettingsTabLoggingEnableFsAccessLogs": "אפשר רישום גישת קבצי מערכת", + "SettingsTabLoggingFsGlobalAccessLogMode": "מצב רישום גלובלי של גישת קבצי מערכת", + "SettingsTabLoggingDeveloperOptions": "אפשרויות מפתח", + "SettingsTabLoggingDeveloperOptionsNote": "אזהרה: יפחית ביצועים", + "SettingsTabLoggingGraphicsBackendLogLevel": "רישום גרפיקת קצה אחורי:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "כלום", + "SettingsTabLoggingGraphicsBackendLogLevelError": "שגיאה", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "האטות", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "הכל", + "SettingsTabLoggingEnableDebugLogs": "אפשר רישום ניפוי באגים", + "SettingsTabInput": "קלט", + "SettingsTabInputEnableDockedMode": "מצב עגינה", + "SettingsTabInputDirectKeyboardAccess": "גישה ישירה למקלדת", + "SettingsButtonSave": "שמירה", + "SettingsButtonClose": "סגירה", + "SettingsButtonOk": "אישור", + "SettingsButtonCancel": "ביטול", + "SettingsButtonApply": "החל", + "ControllerSettingsPlayer": "שחקן/ית", + "ControllerSettingsPlayer1": "שחקן/ית 1", + "ControllerSettingsPlayer2": "שחקן/ית 2", + "ControllerSettingsPlayer3": "שחקן/ית 3", + "ControllerSettingsPlayer4": "שחקן/ית 4", + "ControllerSettingsPlayer5": "שחקן/ית 5", + "ControllerSettingsPlayer6": "שחקן/ית 6", + "ControllerSettingsPlayer7": "שחקן/ית 7", + "ControllerSettingsPlayer8": "שחקן/ית 8", + "ControllerSettingsHandheld": "נייד", + "ControllerSettingsInputDevice": "מכשיר קלט", + "ControllerSettingsRefresh": "רענון", + "ControllerSettingsDeviceDisabled": "מושבת", + "ControllerSettingsControllerType": "סוג שלט", + "ControllerSettingsControllerTypeHandheld": "נייד", + "ControllerSettingsControllerTypeProController": "שלט פרו ", + "ControllerSettingsControllerTypeJoyConPair": "ג'ויקון הותאם", + "ControllerSettingsControllerTypeJoyConLeft": "ג'ויקון שמאלי ", + "ControllerSettingsControllerTypeJoyConRight": "ג'ויקון ימני", + "ControllerSettingsProfile": "פרופיל", + "ControllerSettingsProfileDefault": "ברירת המחדל", + "ControllerSettingsLoad": "טעינה", + "ControllerSettingsAdd": "הוספה", + "ControllerSettingsRemove": "הסר", + "ControllerSettingsButtons": "כפתורים", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "כפתורי כיוונים", + "ControllerSettingsDPadUp": "מעלה", + "ControllerSettingsDPadDown": "מטה", + "ControllerSettingsDPadLeft": "שמאלה", + "ControllerSettingsDPadRight": "ימינה", + "ControllerSettingsStickButton": "כפתור", + "ControllerSettingsStickUp": "למעלה", + "ControllerSettingsStickDown": "למטה", + "ControllerSettingsStickLeft": "שמאלה", + "ControllerSettingsStickRight": "ימינה", + "ControllerSettingsStickStick": "סטיק", + "ControllerSettingsStickInvertXAxis": "הפיכת הX של הסטיק", + "ControllerSettingsStickInvertYAxis": "הפיכת הY של הסטיק", + "ControllerSettingsStickDeadzone": "שטח מת:", + "ControllerSettingsLStick": "מקל שמאלי", + "ControllerSettingsRStick": "מקל ימני", + "ControllerSettingsTriggersLeft": "הדק שמאלי", + "ControllerSettingsTriggersRight": "הדק ימני", + "ControllerSettingsTriggersButtonsLeft": "כפתור הדק שמאלי", + "ControllerSettingsTriggersButtonsRight": "כפתור הדק ימני", + "ControllerSettingsTriggers": "הדקים", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "כפתורים משמאל", + "ControllerSettingsExtraButtonsRight": "כפתורים מימין", + "ControllerSettingsMisc": "שונות", + "ControllerSettingsTriggerThreshold": "סף הדק:", + "ControllerSettingsMotion": "תנועה", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "השתמש בתנועת CemuHook תואמת ", + "ControllerSettingsMotionControllerSlot": "מיקום שלט", + "ControllerSettingsMotionMirrorInput": "קלט מראה", + "ControllerSettingsMotionRightJoyConSlot": "מיקום ג'ויקון ימני", + "ControllerSettingsMotionServerHost": "מארח השרת:", + "ControllerSettingsMotionGyroSensitivity": "רגישות ג'ירוסקופ:", + "ControllerSettingsMotionGyroDeadzone": "שטח מת של הג'ירוסקופ:", + "ControllerSettingsSave": "שמירה", + "ControllerSettingsClose": "סגירה", + "UserProfilesSelectedUserProfile": "פרופיל המשתמש הנבחר:", + "UserProfilesSaveProfileName": "שמור שם פרופיל", + "UserProfilesChangeProfileImage": "שנה תמונת פרופיל", + "UserProfilesAvailableUserProfiles": "פרופילי משתמש זמינים:", + "UserProfilesAddNewProfile": "צור פרופיל", + "UserProfilesDelete": "מחיקה", + "UserProfilesClose": "סגור", + "ProfileNameSelectionWatermark": "בחרו כינוי", + "ProfileImageSelectionTitle": "בחירת תמונת פרופיל", + "ProfileImageSelectionHeader": "בחרו תמונת פרופיל", + "ProfileImageSelectionNote": "אתם יכולים לייבא תמונת פרופיל מותאמת אישית, או לבחור אווטאר מקושחת המערכת", + "ProfileImageSelectionImportImage": "ייבוא קובץ תמונה", + "ProfileImageSelectionSelectAvatar": "בחרו אוואטר קושחה", + "InputDialogTitle": "דיאלוג קלט", + "InputDialogOk": "בסדר", + "InputDialogCancel": "ביטול", + "InputDialogAddNewProfileTitle": "בחרו את שם הפרופיל", + "InputDialogAddNewProfileHeader": "אנא הזינו שם לפרופיל", + "InputDialogAddNewProfileSubtext": "(אורך מרבי: {0})", + "AvatarChoose": "בחרו דמות", + "AvatarSetBackgroundColor": "הגדר צבע רקע", + "AvatarClose": "סגור", + "ControllerSettingsLoadProfileToolTip": "טען פרופיל", + "ControllerSettingsAddProfileToolTip": "הוסף פרופיל", + "ControllerSettingsRemoveProfileToolTip": "הסר פרופיל", + "ControllerSettingsSaveProfileToolTip": "שמור פרופיל", + "MenuBarFileToolsTakeScreenshot": "צלם מסך", + "MenuBarFileToolsHideUi": "הסתר ממשק משתמש ", + "GameListContextMenuRunApplication": "הרץ יישום", + "GameListContextMenuToggleFavorite": "למתג העדפה", + "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", + "SettingsTabGeneralTheme": "ערכת נושא", + "SettingsTabGeneralThemeCustomTheme": "נתיב ערכת נושא מותאמת אישית", + "SettingsTabGeneralThemeBaseStyle": "סגנון בסיס", + "SettingsTabGeneralThemeBaseStyleDark": "כהה", + "SettingsTabGeneralThemeBaseStyleLight": "בהיר", + "SettingsTabGeneralThemeEnableCustomTheme": "אפשר ערכת נושא מותאמת אישית", + "ButtonBrowse": "עיין", + "ControllerSettingsConfigureGeneral": "הגדר", + "ControllerSettingsRumble": "רטט", + "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", + "ControllerSettingsRumbleWeakMultiplier": "מכפיל רטט חלש", + "DialogMessageSaveNotAvailableMessage": "אין שמור משחק עבור [{1:x16}] {0}", + "DialogMessageSaveNotAvailableCreateSaveMessage": "האם תרצה ליצור שמור משחק עבור המשחק הזה?", + "DialogConfirmationTitle": "ריוג'ינקס - אישור", + "DialogUpdaterTitle": "ריוג'ינקס - מעדכן", + "DialogErrorTitle": "ריוג'ינקס - שגיאה", + "DialogWarningTitle": "ריוג'ינקס - אזהרה", + "DialogExitTitle": "ריוג'ינקס - יציאה", + "DialogErrorMessage": "ריוג'ינקס נתקל בשגיאה", + "DialogExitMessage": "האם אתם בטוחים שאתם רוצים לסגור את ריוג'ינקס?", + "DialogExitSubMessage": "כל הנתונים שלא נשמרו יאבדו!", + "DialogMessageCreateSaveErrorMessage": "אירעה שגיאה ביצירת שמור המשחק שצויין: {0}", + "DialogMessageFindSaveErrorMessage": "אירעה שגיאה במציאת שמור המשחק שצויין: {0}", + "FolderDialogExtractTitle": "בחרו את התיקייה לחילוץ", + "DialogNcaExtractionMessage": "מלחץ {0} ממקטע {1}...", + "DialogNcaExtractionTitle": "ריוג'ינקס - מחלץ מקטע NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "כשל בחילוץ. ה-NCA הראשי לא היה קיים בקובץ שנבחר.", + "DialogNcaExtractionCheckLogErrorMessage": "כשל בחילוץ. קרא את קובץ הרישום למידע נוסף.", + "DialogNcaExtractionSuccessMessage": "החילוץ הושלם בהצלחה.", + "DialogUpdaterConvertFailedMessage": "המרת הגרסה הנוכחית של ריוג'ינקס נכשלה.", + "DialogUpdaterCancelUpdateMessage": "מבטל עדכון!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "אתם כבר משתמשים בגרסה המעודכנת ביותר של ריוג'ינקס!", + "DialogUpdaterFailedToGetVersionMessage": "אירעה שגיאה בעת ניסיון לקבל עדכונים מ-גיטהב. זה יכול להיגרם אם הגרסה המעודכנת האחרונה נוצרה על ידי פעולות של גיטהב. נסה שוב בעוד מספר דקות.", + "DialogUpdaterConvertFailedGithubMessage": "המרת גרסת ריוג'ינקס שהתקבלה מ-עדכון הגרסאות של גיטהב נכשלה.", + "DialogUpdaterDownloadingMessage": "מוריד עדכון...", + "DialogUpdaterExtractionMessage": "מחלץ עדכון...", + "DialogUpdaterRenamingMessage": "משנה את שם העדכון...", + "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", + "DialogUpdaterCompleteMessage": "העדכון הושלם!", + "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", + "DialogUpdaterArchNotSupportedMessage": "אינך מריץ ארכיטקטורת מערכת נתמכת!", + "DialogUpdaterArchNotSupportedSubMessage": "(רק מערכות x64 נתמכות!)", + "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", + "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", + "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", + "DialogUpdaterDirtyBuildSubMessage": "אם אתם מחפשים גרסא נתמכת, אנא הורידו את ריוג'ינקס בכתובת https://ryujinx.org", + "DialogRestartRequiredMessage": "אתחול נדרש", + "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", + "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", + "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \\nהאמולטור יופעל כעת.", + "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", + "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", + "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", + "DialogInstallFileTypesErrorMessage": "נכשל בהתקנת סוגי קבצים.", + "DialogUninstallFileTypesSuccessMessage": "סוגי קבצים הוסרו בהצלחה!", + "DialogUninstallFileTypesErrorMessage": "נכשל בהסרת סוגי קבצים.", + "DialogOpenSettingsWindowLabel": "פתח את חלון ההגדרות", + "DialogControllerAppletTitle": "יישומון בקר", + "DialogMessageDialogErrorExceptionMessage": "שגיאה בהצגת דיאלוג ההודעה: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "שגיאה בהצגת תוכנת המקלדת: {0}", + "DialogErrorAppletErrorExceptionMessage": "שגיאה בהצגת דיאלוג ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nלמידע נוסף על איך לתקן שגיאה זו, עקוב אחר מדריך ההתקנה שלנו.", + "DialogUserErrorDialogTitle": "שגיאת Ryujinx ({0})", + "DialogAmiiboApiTitle": "ממשק תכנות אמיבו", + "DialogAmiiboApiFailFetchMessage": "אירעה שגיאה בעת שליפת מידע מהממשק.", + "DialogAmiiboApiConnectErrorMessage": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.", + "DialogProfileInvalidProfileErrorMessage": "הפרופיל {0} אינו תואם למערכת תצורת הקלט הנוכחית.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "לא ניתן להחליף את פרופיל ברירת המחדל", + "DialogProfileDeleteProfileTitle": "מוחק פרופיל", + "DialogProfileDeleteProfileMessage": "פעולה זו היא בלתי הפיכה, האם אתם בטוחים שברצונכם להמשיך?", + "DialogWarning": "אזהרה", + "DialogPPTCDeletionMessage": "אם תמשיכו אתם עומדים לגרום לבנייה מחדש של מטמון ה-PPTC עבור:\n\n{0}", + "DialogPPTCDeletionErrorMessage": "שגיאה בטיהור מטמון PPTC ב-{0}: {1}", + "DialogShaderDeletionMessage": "אם תמשיכו אתם עומדים למחוק את מטמון ההצללות עבור:\n\n{0}", + "DialogShaderDeletionErrorMessage": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "DialogRyujinxErrorMessage": "ריוג'ינקס נתקלה בשגיאה", + "DialogInvalidTitleIdErrorMessage": "שגיאת ממשק משתמש: למשחק שנבחר לא קיים מזהה משחק", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "לא נמצאה קושחת מערכת תקפה ב-{0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "התקן קושחה {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "גירסת המערכת {0} תותקן.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nהאם ברצונך להמשיך?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "מתקין קושחה...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "גרסת המערכת {0} הותקנה בהצלחה.", + "DialogUserProfileDeletionWarningMessage": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק", + "DialogUserProfileDeletionConfirmMessage": "האם ברצונך למחוק את הפרופיל שנבחר", + "DialogUserProfileUnsavedChangesTitle": "אזהרה - שינויים לא שמורים", + "DialogUserProfileUnsavedChangesMessage": "ביצעת שינויים בפרופיל משתמש זה שלא נשמרו.", + "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", + "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", + "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", + "DialogLoadNcaErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", + "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "הפעלת השלכת הצללה, שנועדה לשמש מפתחים בלבד.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את השלכת הצללה. האם ברצונך להשבית את השלכת הצללה עכשיו?", + "DialogLoadAppGameAlreadyLoadedMessage": "ישנו משחק שכבר רץ כעת", + "DialogLoadAppGameAlreadyLoadedSubMessage": "אנא הפסק את האמולציה או סגור את האמולטור לפני הפעלת משחק אחר.", + "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", + "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", + "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", + "SettingsTabGraphicsFeaturesOptions": "אפשרויות", + "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", + "CommonAuto": "אוטומטי", + "CommonOff": "כבוי", + "CommonOn": "דלוק", + "InputDialogYes": "כן", + "InputDialogNo": "לא", + "DialogProfileInvalidProfileNameErrorMessage": "שם הקובץ מכיל תווים לא חוקיים. אנא נסה שוב.", + "MenuBarOptionsPauseEmulation": "הפסק", + "MenuBarOptionsResumeEmulation": "המשך", + "AboutUrlTooltipMessage": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDisclaimerMessage": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.", + "AboutAmiiboDisclaimerMessage": "ממשק אמיבו (www.amiiboapi.com) משומש בהדמיית האמיבו שלנו.", + "AboutPatreonUrlTooltipMessage": "לחץ כדי לפתוח את דף הפטראון של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutGithubUrlTooltipMessage": "לחץ כדי לפתוח את דף הגיטהב של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDiscordUrlTooltipMessage": "לחץ כדי לפתוח הזמנה לשרת הדיסקורד של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutTwitterUrlTooltipMessage": "לחץ כדי לפתוח את דף הטוויטר של Ryujinx בדפדפן ברירת המחדל שלך.", + "AboutRyujinxAboutTitle": "אודות:", + "AboutRyujinxAboutContent": "ריוג'ינקס הוא אמולטור עבור הנינטנדו סוויץ' (כל הזכויות שמורות).\nבבקשה תתמכו בנו בפטראון.\nקבל את כל החדשות האחרונות בטוויטר או בדיסקורד שלנו.\nמפתחים המעוניינים לתרום יכולים לקבל מידע נוסף ב-גיטהאב או ב-דיסקורד שלנו.", + "AboutRyujinxMaintainersTitle": "מתוחזק על ידי:", + "AboutRyujinxMaintainersContentTooltipMessage": "לחץ כדי לפתוח את דף התורמים בדפדפן ברירת המחדל שלך.", + "AboutRyujinxSupprtersTitle": "תמוך באמצעות Patreon", + "AmiiboSeriesLabel": "סדרת אמיבו", + "AmiiboCharacterLabel": "דמות", + "AmiiboScanButtonLabel": "סרוק את זה", + "AmiiboOptionsShowAllLabel": "הצג את כל האמיבואים", + "AmiiboOptionsUsRandomTagLabel": "האצה: השתמש בתג Uuid אקראי", + "DlcManagerTableHeadingEnabledLabel": "מאופשר", + "DlcManagerTableHeadingTitleIdLabel": "מזהה משחק", + "DlcManagerTableHeadingContainerPathLabel": "נתיב מכיל", + "DlcManagerTableHeadingFullPathLabel": "נתיב מלא", + "DlcManagerRemoveAllButton": "מחק הכל", + "DlcManagerEnableAllButton": "אפשר הכל", + "DlcManagerDisableAllButton": "השבת הכל", + "MenuBarOptionsChangeLanguage": "החלף שפה", + "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", + "CommonSort": "מיין", + "CommonShowNames": "הצג שמות", + "CommonFavorite": "מועדף", + "OrderAscending": "סדר עולה", + "OrderDescending": "סדר יורד", + "SettingsTabGraphicsFeatures": "תכונות ושיפורים", + "ErrorWindowTitle": "חלון שגיאה", + "ToggleDiscordTooltip": "בחרו להציג את ריוג'ינקס או לא בפעילות הדיסקורד שלכם \"משוחק כרגע\".", + "AddGameDirBoxTooltip": "הזן תקיית משחקים כדי להוסיף לרשימה", + "AddGameDirTooltip": "הוסף תקיית משחקים לרשימה", + "RemoveGameDirTooltip": "הסר את תקיית המשחקים שנבחרה", + "CustomThemeCheckTooltip": "השתמש בעיצוב מותאם אישית של אבלוניה עבור ה-ממשק הגראפי כדי לשנות את המראה של תפריטי האמולטור", + "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", + "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", + "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", + "DirectKeyboardTooltip": "תמיכה ישירה למקלדת (HID). מספק גישה בשביל משחקים למקלדת שלך כמכשיר להזנת טקסט.", + "DirectMouseTooltip": "תמיכה ישירה לעכבר (HID). מספק גישה בשביל משחקים לעכבר שלך כהתקן הצבעה.", + "RegionTooltip": "שנה אזור מערכת", + "LanguageTooltip": "שנה שפת מערכת", + "TimezoneTooltip": "שנה את אזור הזמן של המערכת", + "TimeTooltip": "שנה זמן מערכת", + "VSyncToggleTooltip": "מדמה סנכרון אנכי של קונסולה. כלומר חסם הפריימים לרוב המשחקים; השבתה שלו עלולה לגרום למשחקים לרוץ מהר יותר או לגרום למסכי טעינה לקחת יותר זמן או להתקע.\n\nניתן לשנות מצב של תפריט זה בזמן משחק עם המקש לבחירתך. אנו ממליצים לעשות זאת אם אתם מתכננים להשבית אותו.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", + "MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.", + "MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.", + "MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.", + "MemoryManagerUnsafeTooltip": "ממפה זיכרון ישירות, אך לא ממסך את הכתובת בתוך כתובת המארח לפני הגישה. מהיר, אך במחיר של הגנה. יישום המארח בעל גישה לזיכרון מכל מקום בריוג'ינקס, לכן הריצו איתו רק קבצים שאתם סומכים עליהם.", + "UseHypervisorTooltip": "השתמש ב- Hypervisor במקום JIT. משפר מאוד ביצועים כשניתן, אבל יכול להיות לא יציב במצבו הנוכחי.", + "DRamTooltip": "מנצל תצורת מצב-זיכרון חלופית לחכות את מכשיר הפיתוח של הסוויץ'.\n\nזה שימושי להחלפת חבילות מרקמים באיכותיים יותר או כאלו ברזולוציית 4k. לא משפר ביצועים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "IgnoreMissingServicesTooltip": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "ResolutionScaleTooltip": "שיפור רזולוצייה המאופשרת לעיבוד מטרות.", + "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", + "AnisotropyTooltip": "רמת סינון אניסוטרופי (מוגדר לאוטומטי כדי להשתמש בערך המבוקש על ידי המשחק)", + "AspectRatioTooltip": "יחס גובה-רוחב הוחל על חלון המעבד.", + "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", + "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", + "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", + "InfoLogTooltip": "מדפיק רישומי מידע לשורת הפקודות. לא משפיע על ביצועי היישום.", + "WarnLogTooltip": "מדפיק רישומי הערות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "ErrorLogTooltip": "מדפיס רישומי שגיאות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "TraceLogTooltip": "מדפיק רישומי זיכרון לשורת הפקודות. לא משפיע על ביצועי היישום.", + "GuestLogTooltip": "מדפיס רישומי אורח לשורת הפקודות. לא משפיע על ביצועי היישום.", + "FileAccessLogTooltip": "מדפיס גישות לקבצי רישום לשורת הפקודות.", + "FSAccessLogModeTooltip": "מאפשר גישה לרישומי FS ליציאת שורת הפקודות. האפשרויות הינן 0-3.", + "DeveloperOptionTooltip": "השתמש בזהירות", + "OpenGlLogLevel": "דורש הפעלת רמות רישום מתאימות", + "DebugLogTooltip": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", + "LoadApplicationFileTooltip": "פתח סייר קבצים כדי לבחור קובץ תואם סוויץ' לטעינה", + "LoadApplicationFolderTooltip": "פתח סייר קבצים כדי לבחור יישום תואם סוויץ', לא ארוז לטעינה.", + "OpenRyujinxFolderTooltip": "פתח את תיקיית מערכת הקבצים ריוג'ינקס", + "OpenRyujinxLogsTooltip": "פותח את התיקיה שאליה נכתבים רישומים", + "ExitTooltip": "צא מריוג'ינקס", + "OpenSettingsTooltip": "פתח את חלון ההגדרות", + "OpenProfileManagerTooltip": "פתח את חלון מנהל פרופילי המשתמש", + "StopEmulationTooltip": "הפסק את הדמייה של המשחק הנוכחי וחזור למסך בחירת המשחק", + "CheckUpdatesTooltip": "בדוק אם קיימים עדכונים לריוג'ינקס", + "OpenAboutTooltip": "פתח את חלון אודות היישום", + "GridSize": "גודל רשת", + "GridSizeTooltip": "שנה את גודל המוצרים על הרשת.", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "פורטוגלית ברזילאית", + "AboutRyujinxContributorsButtonHeader": "צפה בכל התורמים", + "SettingsTabSystemAudioVolume": "עוצמת קול: ", + "AudioVolumeTooltip": "שנה עוצמת קול", + "SettingsTabSystemEnableInternetAccess": "אפשר גישה לאינטרנט בתור אורח/חיבור לאן", + "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", + "GameListContextMenuManageCheat": "נהל צ'יטים", + "ControllerSettingsStickRange": "טווח:", + "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", + "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", + "SettingsTabCpu": "מעבד", + "SettingsTabAudio": "שמע", + "SettingsTabNetwork": "רשת", + "SettingsTabNetworkConnection": "חיבור רשת", + "SettingsTabCpuCache": "מטמון מעבד", + "SettingsTabCpuMemory": "מצב מעבד", + "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", + "UpdaterDisabledWarningTitle": "מעדכן מושבת!", + "GameListContextMenuOpenSdModsDirectory": "פתח את תקיית המודים של אטמוספרה", + "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תקיית כרטיס ה-SD החלופית של אטמוספרה המכילה את המודים של האפליקציה. שימושי עבור מודים ארוזים עבור חומרה אמיתית.", + "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", + "IconSize": "גודל הסמל", + "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", + "MenuBarOptionsShowConsole": "הצג שורת פקודות", + "ShaderCachePurgeError": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "UserErrorNoKeys": "המפתחות לא נמצאו", + "UserErrorNoFirmware": "קושחה לא נמצאה", + "UserErrorFirmwareParsingFailed": "שגיאת ניתוח קושחה", + "UserErrorApplicationNotFound": "יישום לא נמצא", + "UserErrorUnknown": "שגיאה לא ידועה", + "UserErrorUndefined": "שגיאה לא מוגדרת", + "UserErrorNoKeysDescription": "ריוג'ינקס לא הצליח למצוא את קובץ ה-'prod.keys' שלך", + "UserErrorNoFirmwareDescription": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת", + "UserErrorFirmwareParsingFailedDescription": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.", + "UserErrorApplicationNotFoundDescription": "ריוג'ינקס לא מצאה יישום תקין בנתיב הנתון", + "UserErrorUnknownDescription": "קרתה שגיאה לא ידועה!", + "UserErrorUndefinedDescription": "קרתה שגיאה לא מוגדרת! זה לא אמור לקרות, אנא צרו קשר עם מפתח!", + "OpenSetupGuideMessage": "פתח מדריך התקנה", + "NoUpdate": "אין עדכון", + "TitleUpdateVersionLabel": "גרסה {0}", + "RyujinxInfo": "ריוג'ינקס - מידע", + "RyujinxConfirm": "ריוג'ינקס - אישור", + "FileDialogAllTypes": "כל הסוגים", + "Never": "אף פעם", + "SwkbdMinCharacters": "לפחות {0} תווים", + "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", + "SoftwareKeyboard": "מקלדת וירטואלית", + "SoftwareKeyboardModeNumbersOnly": "מחויב להיות מספרי בלבד", + "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", + "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", + "DialogControllerAppletMessagePlayerRange": "האפליקציה מבקשת {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", + "DialogControllerAppletMessage": "האפליקציה מבקשת בדיוק {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", + "DialogControllerAppletDockModeSet": "במצב עגינה. בנוסף מצב נייד לא אפשרי.\n\n", + "UpdaterRenaming": "משנה שמות של קבצים ישנים...", + "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", + "UpdaterAddingFiles": "מוסיף קבצים חדשים...", + "UpdaterExtracting": "מחלץ עדכון...", + "UpdaterDownloading": "מוריד עדכון...", + "Game": "משחק", + "Docked": "בתחנת עגינה", + "Handheld": "נייד", + "ConnectionError": "שגיאת חיבור", + "AboutPageDeveloperListMore": "{0} ועוד...", + "ApiError": "שגיאת ממשק.", + "LoadingHeading": "טוען {0}", + "CompilingPPTC": "קימפול PTC", + "CompilingShaders": "קימפול הצללות", + "AllKeyboards": "כל המקלדות", + "OpenFileDialogTitle": "בחר קובץ נתמך לפתיחה", + "OpenFolderDialogTitle": "בחר תיקיה עם משחק לא ארוז", + "AllSupportedFormats": "כל הפורמטים הנתמכים", + "RyujinxUpdater": "מעדכן ריוג'ינקס", + "SettingsTabHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysToggleVsyncHotkey": "שנה סינכרון אנכי:", + "SettingsTabHotkeysScreenshotHotkey": "צילום מסך:", + "SettingsTabHotkeysShowUiHotkey": "הצג ממשק משתמש:", + "SettingsTabHotkeysPauseHotkey": "הפסק:", + "SettingsTabHotkeysToggleMuteHotkey": "השתק:", + "ControllerMotionTitle": "הגדרות שליטת תנועות ג'ירוסקופ", + "ControllerRumbleTitle": "הגדרות רטט", + "SettingsSelectThemeFileDialogTitle": "בחרו קובץ ערכת נושא", + "SettingsXamlThemeFile": "קובץ ערכת נושא Xaml", + "AvatarWindowTitle": "ניהול חשבונות - אוואטר", + "Amiibo": "אמיבו", + "Unknown": "לא ידוע", + "Usage": "שימוש", + "Writable": "ניתן לכתיבה", + "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", + "SelectUpdateDialogTitle": "בחרו קבצי עדכון", + "UserProfileWindowTitle": "ניהול פרופילי משתמש", + "CheatWindowTitle": "נהל צ'יטים למשחק", + "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", + "UpdateWindowTitle": "נהל עדכוני משחקים", + "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", + "BuildId": "מזהה בניה:", + "DlcWindowHeading": "{0} הרחבות משחק", + "UserProfilesEditProfile": "ערוך נבחר/ים", + "Cancel": "בטל", + "Save": "שמור", + "Discard": "השלך", + "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", + "UserProfileEmptyNameError": "נדרש שם", + "UserProfileNoImageError": "נדרשת תמונת פרופיל", + "GameUpdateWindowHeading": "נהל עדכונים עבור {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "שפר רזולוציה:", + "SettingsTabHotkeysResScaleDownHotkey": "הפחת רזולוציה:", + "UserProfilesName": "שם:", + "UserProfilesUserId": "מזהה משתמש:", + "SettingsTabGraphicsBackend": "אחראי גראפיקה", + "SettingsTabGraphicsBackendTooltip": "אחראי גראפיקה לשימוש", + "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", + "SettingsEnableTextureRecompressionTooltip": " דוחס מרקמים מסויימים להפחתת השימוש בראם הוירטואלי.\n\nמומלץ לשימוש עם כרטיס גראפי בעל פחות מ-4GiB בראם הוירטואלי.\n\nמוטב להשאיר כבוי אם אינכם בטוחים.", + "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", + "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", + "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", + "SettingsGpuBackendRestartMessage": "הגדרות אחראי גרפיקה או כרטיס גראפי שונו. זה ידרוש הפעלה מחדש כדי להחיל שינויים", + "SettingsGpuBackendRestartSubMessage": "האם ברצונך להפעיל מחדש כעט?", + "RyujinxUpdaterMessage": "האם ברצונך לעדכן את ריוג'ינקס לגרסא האחרונה?", + "SettingsTabHotkeysVolumeUpHotkey": "הגבר את עוצמת הקול:", + "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "שמע", + "UserProfilesManageSaves": "נהל שמורים", + "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", + "IrreversibleActionNote": "הפעולה הזו בלתי הפיכה.", + "SaveManagerHeading": "נהל שמורי משחק עבור {0} ({1})", + "SaveManagerTitle": "מנהל שמירות", + "Name": "שם", + "Size": "גודל", + "Search": "חפש", + "UserProfilesRecoverLostAccounts": "שחזר חשבון שאבד", + "Recover": "שחזר", + "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", + "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", + "GraphicsAATooltip": "מחיל החלקת-עקומות על עיבוד המשחק", + "GraphicsAALabel": "החלקת-עקומות:", + "GraphicsScalingFilterLabel": "מסנן מידת איכות:", + "GraphicsScalingFilterTooltip": "אפשר מידת איכות מסוג איגור-תמונה", + "GraphicsScalingFilterLevelLabel": "רמה", + "GraphicsScalingFilterLevelTooltip": "קבע מידת איכות תמונה לפי רמת סינון", + "SmaaLow": "SMAA נמוך", + "SmaaMedium": "SMAA בינוני", + "SmaaHigh": "SMAA גבוהה", + "SmaaUltra": "SMAA אולטרה", + "UserEditorTitle": "ערוך משתמש", + "UserEditorTitleCreate": "צור משתמש", + "SettingsTabNetworkInterface": "ממשק רשת", + "NetworkInterfaceTooltip": "ממשק הרשת המשומש עבור יכולות לאן", + "NetworkInterfaceDefault": "ברירת המחדל", + "PackagingShaders": "אורז הצללות", + "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", + "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json new file mode 100644 index 00000000..5aff7a7e --- /dev/null +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -0,0 +1,656 @@ +{ + "Language": "Italiano", + "MenuBarFileOpenApplet": "Apri Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", + "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", + "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", + "SettingsTabSystemUseHypervisor": "Usa Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Carica applicazione da un file", + "MenuBarFileOpenUnpacked": "Carica _gioco estratto", + "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", + "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", + "MenuBarFileExit": "_Esci", + "MenuBarOptions": "Opzioni", + "MenuBarOptionsToggleFullscreen": "Schermo intero", + "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", + "MenuBarOptionsStopEmulation": "Ferma emulazione", + "MenuBarOptionsSettings": "_Impostazioni", + "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", + "MenuBarActions": "_Azioni", + "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", + "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarTools": "_Strumenti", + "MenuBarToolsInstallFirmware": "Installa firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", + "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", + "MenuBarToolsInstallFileTypes": "Installa i tipi di file", + "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", + "MenuBarHelp": "Aiuto", + "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", + "MenuBarHelpAbout": "Informazioni", + "MenuSearch": "Cerca...", + "GameListHeaderFavorite": "Preferito", + "GameListHeaderIcon": "Icona", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Sviluppatore", + "GameListHeaderVersion": "Versione", + "GameListHeaderTimePlayed": "Tempo di gioco", + "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderFileExtension": "Estensione", + "GameListHeaderFileSize": "Dimensione file", + "GameListHeaderPath": "Percorso", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", + "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", + "GameListContextMenuManageDlc": "Gestici DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", + "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuCacheManagement": "Gestione della cache", + "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", + "GameListContextMenuExtractData": "Estrai dati", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "StatusBarSystemVersion": "Versione di sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", + "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sì, in modo permanente", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantità massima di mappature di memoria è inferiore a quella consigliata.", + "LinuxVmMaxMapCountWarningTextSecondary": "Il valore corrente di vm.max_map_count ({0}) è inferiore a {1}. Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.\n\nPotresti voler aumentare manualmente il limite o installare pkexec, il che permette a Ryujinx di assisterlo.", + "Settings": "Impostazioni", + "SettingsTabGeneral": "Interfaccia utente", + "SettingsTabGeneralGeneral": "Generali", + "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", + "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralHideCursor": "Nascondi il cursore:", + "SettingsTabGeneralHideCursorNever": "Mai", + "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", + "SettingsTabGeneralAdd": "Aggiungi", + "SettingsTabGeneralRemove": "Rimuovi", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principale", + "SettingsTabSystemSystemRegion": "Regione del sistema:", + "SettingsTabSystemSystemRegionJapan": "Giappone", + "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Cina", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Lingua del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Giapponese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", + "SettingsTabSystemSystemLanguageFrench": "Francese", + "SettingsTabSystemSystemLanguageGerman": "Tedesco", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", + "SettingsTabSystemSystemLanguageChinese": "Cinese", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Olandese", + "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", + "SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:", + "SettingsTabSystemSystemTime": "Data e ora del sistema:", + "SettingsTabSystemEnableVsync": "Attiva VSync", + "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", + "SettingsTabSystemAudioBackend": "Backend audio:", + "SettingsTabSystemAudioBackendDummy": "Manichino", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "AudioIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Trucchi", + "SettingsTabSystemHacksNote": " (Possono causare instabilità)", + "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", + "SettingsTabGraphics": "Grafica", + "SettingsTabGraphicsAPI": "API Grafiche", + "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", + "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", + "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:", + "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", + "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Attiva modalità TV", + "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", + "SettingsButtonSave": "Salva", + "SettingsButtonClose": "Chiudi", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancella", + "SettingsButtonApply": "Applica", + "ControllerSettingsPlayer": "Giocatore", + "ControllerSettingsPlayer1": "Giocatore 1", + "ControllerSettingsPlayer2": "Giocatore 2", + "ControllerSettingsPlayer3": "Giocatore 3", + "ControllerSettingsPlayer4": "Giocatore 4", + "ControllerSettingsPlayer5": "Giocatore 5", + "ControllerSettingsPlayer6": "Giocatore 6", + "ControllerSettingsPlayer7": "Giocatore 7", + "ControllerSettingsPlayer8": "Giocatore 8", + "ControllerSettingsHandheld": "Portatile", + "ControllerSettingsInputDevice": "Dispositivo di input", + "ControllerSettingsRefresh": "Ricarica", + "ControllerSettingsDeviceDisabled": "Disabilitato", + "ControllerSettingsControllerType": "Tipo di controller", + "ControllerSettingsControllerTypeHandheld": "Portatile", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", + "ControllerSettingsProfile": "Profilo", + "ControllerSettingsProfileDefault": "Predefinito", + "ControllerSettingsLoad": "Carica", + "ControllerSettingsAdd": "Aggiungi", + "ControllerSettingsRemove": "Rimuovi", + "ControllerSettingsButtons": "Pulsanti", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croce direzionale", + "ControllerSettingsDPadUp": "Su", + "ControllerSettingsDPadDown": "Giù", + "ControllerSettingsDPadLeft": "Sinistra", + "ControllerSettingsDPadRight": "Destra", + "ControllerSettingsStickButton": "Pulsante", + "ControllerSettingsStickUp": "Su", + "ControllerSettingsStickDown": "Giù", + "ControllerSettingsStickLeft": "Sinistra", + "ControllerSettingsStickRight": "Destra", + "ControllerSettingsStickStick": "Levetta", + "ControllerSettingsStickInvertXAxis": "Inverti levetta X", + "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", + "ControllerSettingsStickDeadzone": "Zona morta:", + "ControllerSettingsLStick": "Stick sinistro", + "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsTriggersLeft": "Grilletto sinistro", + "ControllerSettingsTriggersRight": "Grilletto destro", + "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", + "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", + "ControllerSettingsTriggers": "Grilletti", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsRight": "Tasto destro", + "ControllerSettingsMisc": "Varie", + "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", + "ControllerSettingsMotion": "Movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot del controller:", + "ControllerSettingsMotionMirrorInput": "Input specchiato", + "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", + "ControllerSettingsMotionServerHost": "Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", + "ControllerSettingsSave": "Salva", + "ControllerSettingsClose": "Chiudi", + "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", + "UserProfilesSaveProfileName": "Salva nome del profilo", + "UserProfilesChangeProfileImage": "Cambia immagine profilo", + "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", + "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", + "UserProfilesDelete": "Elimina", + "UserProfilesClose": "Chiudi", + "ProfileNameSelectionWatermark": "Scegli un soprannome", + "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", + "ProfileImageSelectionHeader": "Scegli un'immagine profilo", + "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", + "ProfileImageSelectionImportImage": "Importa file immagine", + "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", + "InputDialogTitle": "Finestra di dialogo ", + "InputDialogOk": "OK", + "InputDialogCancel": "Annulla", + "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileHeader": "Digita un nome profilo", + "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", + "AvatarChoose": "Scegli", + "AvatarSetBackgroundColor": "Imposta colore di sfondo", + "AvatarClose": "Chiudi", + "ControllerSettingsLoadProfileToolTip": "Carica profilo", + "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", + "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", + "ControllerSettingsSaveProfileToolTip": "Salva profilo", + "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi UI", + "GameListContextMenuRunApplication": "Esegui applicazione", + "GameListContextMenuToggleFavorite": "Preferito", + "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", + "SettingsTabGeneralThemeBaseStyle": "Modalità", + "SettingsTabGeneralThemeBaseStyleDark": "Scura", + "SettingsTabGeneralThemeBaseStyleLight": "Chiara", + "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", + "ButtonBrowse": "Sfoglia", + "ControllerSettingsConfigureGeneral": "Configura", + "ControllerSettingsRumble": "Vibrazione", + "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", + "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", + "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?", + "DialogConfirmationTitle": "Ryujinx - Conferma", + "DialogUpdaterTitle": "Ryujinx - Aggiornamento", + "DialogErrorTitle": "Ryujinx - Errore", + "DialogWarningTitle": "Ryujinx - Avviso", + "DialogExitTitle": "Ryujinx - Esci", + "DialogErrorMessage": "Ryujinx ha riscontrato un problema", + "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", + "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", + "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", + "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", + "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", + "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", + "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", + "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", + "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", + "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", + "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", + "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", + "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", + "DialogUpdaterCompleteMessage": "Aggiornamento completato!", + "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", + "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", + "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", + "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", + "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", + "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", + "DialogRestartRequiredMessage": "Riavvio richiesto", + "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", + "DialogThemeRestartSubMessage": "Vuoi riavviare?", + "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", + "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", + "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", + "DialogInstallFileTypesSuccessMessage": "Tipi di file installati con successo!", + "DialogInstallFileTypesErrorMessage": "Impossibile installare i tipi di file.", + "DialogUninstallFileTypesSuccessMessage": "Tipi di file disinstallati con successo!", + "DialogUninstallFileTypesErrorMessage": "Disinstallazione dei tipi di file non riuscita.", + "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", + "DialogControllerAppletTitle": "Applet del controller", + "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", + "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", + "DialogProfileDeleteProfileTitle": "Eliminazione profilo", + "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", + "DialogWarning": "Avviso", + "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", + "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.", + "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", + "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", + "DialogUserProfileUnsavedChangesTitle": "Attenzione - Modifiche Non Salvate", + "DialogUserProfileUnsavedChangesMessage": "Hai apportato modifiche a questo profilo utente che non sono state salvate.", + "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", + "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", + "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", + "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", + "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", + "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", + "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funzionalità", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "CommonAuto": "Auto", + "CommonOff": "Spento", + "CommonOn": "Acceso", + "InputDialogYes": "Si", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Pausa", + "MenuBarOptionsResumeEmulation": "Riprendi", + "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", + "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", + "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", + "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", + "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", + "AboutRyujinxAboutTitle": "Informazioni:", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", + "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", + "AmiiboSeriesLabel": "Serie Amiibo", + "AmiiboCharacterLabel": "Personaggio", + "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "DlcManagerTableHeadingEnabledLabel": "Abilitato", + "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", + "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", + "DlcManagerTableHeadingFullPathLabel": "Percorso completo", + "DlcManagerRemoveAllButton": "Rimuovi tutti", + "DlcManagerEnableAllButton": "Abilita tutto", + "DlcManagerDisableAllButton": "Disabilita tutto", + "MenuBarOptionsChangeLanguage": "Cambia lingua", + "MenuBarShowFileTypes": "Mostra tipi di file", + "CommonSort": "Ordina", + "CommonShowNames": "Mostra nomi", + "CommonFavorite": "Preferito", + "OrderAscending": "Crescente", + "OrderDescending": "Decrescente", + "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", + "ErrorWindowTitle": "Finestra errore", + "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", + "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", + "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", + "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", + "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", + "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", + "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", + "DockModeToggleTooltip": "Attiva o disabilta modalità TV", + "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", + "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", + "RegionTooltip": "Cambia regione di sistema", + "LanguageTooltip": "Cambia lingua di sistema", + "TimezoneTooltip": "Cambia fuso orario di sistema", + "TimeTooltip": "Cambia data e ora di sistema", + "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", + "PptcToggleTooltip": "Attiva o disattiva PPTC", + "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", + "AudioBackendTooltip": "Cambia backend audio", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", + "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", + "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", + "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", + "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", + "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", + "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", + "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", + "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", + "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", + "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", + "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", + "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", + "FileLogTooltip": "Attiva o disattiva il logging su file", + "StubLogTooltip": "Attiva messaggi stub log", + "InfoLogTooltip": "Attiva messaggi info log", + "WarnLogTooltip": "Attiva messaggi warning log", + "ErrorLogTooltip": "Attiva messaggi error log", + "TraceLogTooltip": "Attiva messaggi trace log", + "GuestLogTooltip": "Attiva messaggi guest log", + "FileAccessLogTooltip": "Attiva messaggi file access log", + "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3", + "DeveloperOptionTooltip": "Usa con attenzione", + "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", + "DebugLogTooltip": "Attiva messaggi debug log", + "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", + "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", + "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", + "ExitTooltip": "Esci da Ryujinx", + "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", + "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", + "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", + "OpenAboutTooltip": "Apri finestra delle informazioni", + "GridSize": "Dimensione griglia", + "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", + "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Cambia volume audio", + "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", + "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", + "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", + "GameListContextMenuManageCheat": "Gestisci Cheats", + "ControllerSettingsStickRange": "Raggio:", + "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", + "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Rete", + "SettingsTabNetworkConnection": "Connessione di rete", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Memoria CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater disabilitato!", + "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", + "ControllerSettingsRotate90": "Ruota in senso orario di 90°", + "IconSize": "Dimensioni icona", + "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "MenuBarOptionsShowConsole": "Mostra console", + "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", + "UserErrorNoKeys": "Chiavi non trovate", + "UserErrorNoFirmware": "Firmware non trovato", + "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", + "UserErrorApplicationNotFound": "Applicazione non trovata", + "UserErrorUnknown": "Errore sconosciuto", + "UserErrorUndefined": "Errore non definito", + "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", + "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", + "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "OpenSetupGuideMessage": "Apri la guida all'installazione", + "NoUpdate": "Nessun aggiornamento", + "TitleUpdateVersionLabel": "Versione {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Conferma", + "FileDialogAllTypes": "Tutti i tipi", + "Never": "Mai", + "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", + "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "SoftwareKeyboard": "Tastiera software", + "SoftwareKeyboardModeNumbersOnly": "Deve essere solo numeri", + "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", + "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", + "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", + "UpdaterRenaming": "Rinominazione dei vecchi files...", + "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta nuovi files...", + "UpdaterExtracting": "Estrazione aggiornamento...", + "UpdaterDownloading": "Download aggiornamento...", + "Game": "Gioco", + "Docked": "TV", + "Handheld": "Portatile", + "ConnectionError": "Errore di connessione.", + "AboutPageDeveloperListMore": "{0} e altri ancora...", + "ApiError": "Errore dell'API.", + "LoadingHeading": "Caricamento di {0}", + "CompilingPPTC": "Compilazione PTC", + "CompilingShaders": "Compilazione Shaders", + "AllKeyboards": "Tutte le tastiere", + "OpenFileDialogTitle": "Seleziona un file supportato da aprire", + "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", + "AllSupportedFormats": "Tutti i formati supportati", + "RyujinxUpdater": "Aggiornamento Ryujinx", + "SettingsTabHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", + "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", + "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", + "SettingsTabHotkeysToggleMuteHotkey": "Muta:", + "ControllerMotionTitle": "Impostazioni dei sensori di movimento", + "ControllerRumbleTitle": "Impostazioni di vibrazione", + "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", + "SettingsXamlThemeFile": "File del tema xaml", + "AvatarWindowTitle": "Gestisci account - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Sconosciuto", + "Usage": "Utilizzo", + "Writable": "Scrivibile", + "SelectDlcDialogTitle": "Seleziona file dei DLC", + "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "UserProfileWindowTitle": "Gestisci profili degli utenti", + "CheatWindowTitle": "Gestisci cheat dei giochi", + "DlcWindowTitle": "Gestisci DLC dei giochi", + "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", + "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", + "BuildId": "ID Build", + "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "UserProfilesEditProfile": "Modifica selezionati", + "Cancel": "Annulla", + "Save": "Salva", + "Discard": "Scarta", + "UserProfilesSetProfileImage": "Imposta immagine profilo", + "UserProfileEmptyNameError": "È richiesto un nome", + "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", + "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID utente:", + "SettingsTabGraphicsBackend": "Backend grafica", + "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", + "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", + "SettingsTabGraphicsPreferredGpu": "GPU preferita", + "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", + "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", + "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", + "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", + "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", + "SettingsEnableMacroHLE": "Abilita Macro HLE", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", + "SettingsEnableColorSpacePassthrough": "Spazio colore passante", + "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio colore. Per gli utenti con schermi ad ampia gamma, questo può risultare in colori più vivaci, al costo della correttezza del colore.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gestisci i salvataggi", + "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", + "IrreversibleActionNote": "Questa azione non è reversibile.", + "SaveManagerHeading": "Gestisci i salvataggi per {0}", + "SaveManagerTitle": "Gestione Salvataggi", + "Name": "Nome", + "Size": "Dimensione", + "Search": "Cerca", + "UserProfilesRecoverLostAccounts": "Recupera il tuo account", + "Recover": "Recupera", + "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", + "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", + "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Filtro di scala:", + "GraphicsScalingFilterTooltip": "Abilita scalatura Framebuffer", + "GraphicsScalingFilterLevelLabel": "Livello", + "GraphicsScalingFilterLevelTooltip": "Imposta livello del filtro di scala", + "SmaaLow": "SMAA Basso", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modificare L'Utente", + "UserEditorTitleCreate": "Crea Un Utente", + "SettingsTabNetworkInterface": "Interfaccia di rete:", + "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN", + "NetworkInterfaceDefault": "Predefinito", + "PackagingShaders": "Comprimendo shader", + "AboutChangelogButton": "Visualizza changelog su GitHub", + "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json new file mode 100644 index 00000000..5b31c5f2 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -0,0 +1,656 @@ +{ + "Language": "英語 (アメリカ)", + "MenuBarFileOpenApplet": "アプレットを開く", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", + "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", + "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", + "SettingsTabSystemMemoryManagerModeSoftware": "ソフトウェア", + "SettingsTabSystemMemoryManagerModeHost": "ホスト (高速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ホスト, チェックなし (最高速, 安全でない)", + "SettingsTabSystemUseHypervisor": "ハイパーバイザーを使用", + "MenuBarFile": "ファイル(_F)", + "MenuBarFileOpenFromFile": "ファイルからアプリケーションをロード(_L)", + "MenuBarFileOpenUnpacked": "展開されたゲームをロード", + "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", + "MenuBarFileOpenLogsFolder": "ログフォルダを開く", + "MenuBarFileExit": "終了(_E)", + "MenuBarOptions": "オプション", + "MenuBarOptionsToggleFullscreen": "全画面切り替え", + "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", + "MenuBarOptionsStopEmulation": "エミュレーションを停止", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", + "MenuBarActions": "アクション(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", + "MenuBarActionsScanAmiibo": "Amiibo をスキャン", + "MenuBarTools": "ツール(_T)", + "MenuBarToolsInstallFirmware": "ファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ディレクトリからファームウェアをインストール", + "MenuBarToolsManageFileTypes": "ファイル形式を管理", + "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", + "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", + "MenuBarHelp": "ヘルプ", + "MenuBarHelpCheckForUpdates": "アップデートを確認", + "MenuBarHelpAbout": "Ryujinx について", + "MenuSearch": "検索...", + "GameListHeaderFavorite": "お気に入り", + "GameListHeaderIcon": "アイコン", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "開発元", + "GameListHeaderVersion": "バージョン", + "GameListHeaderTimePlayed": "プレイ時間", + "GameListHeaderLastPlayed": "最終プレイ日時", + "GameListHeaderFileExtension": "ファイル拡張子", + "GameListHeaderFileSize": "ファイルサイズ", + "GameListHeaderPath": "パス", + "GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます", + "GameListContextMenuManageTitleUpdates": "アップデートを管理", + "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", + "GameListContextMenuManageDlc": "DLCを管理", + "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", + "GameListContextMenuCacheManagement": "キャッシュ管理", + "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", + "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", + "GameListContextMenuCacheManagementPurgeShaderCache": "シェーダーキャッシュを破棄", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "アプリケーションのシェーダーキャッシュを破棄します", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC ディレクトリを開く", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "シェーダーキャッシュディレクトリを開く", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "アプリケーションのシェーダーキャッシュを格納するディレクトリを開きます", + "GameListContextMenuExtractData": "データを展開", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", + "GameListContextMenuExtractDataLogo": "ロゴ", + "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "StatusBarGamesLoaded": "{0}/{1} ゲーム", + "StatusBarSystemVersion": "システムバージョン: {0}", + "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count の値を {0}に増やしますか?", + "LinuxVmMaxMapCountDialogTextSecondary": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "はい, 次回再起動まで", + "LinuxVmMaxMapCountDialogButtonPersistent": "はい, 恒久的に", + "LinuxVmMaxMapCountWarningTextPrimary": "メモリマッピングの最大量が推奨値よりも小さいです.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count の現在値 {0} は {1} よりも小さいです. ゲームによっては現在許可されている値よりも大きなメモリマッピングを作成しようとする場合があります. 上限を越えた場合, Ryujinx はクラッシュします.", + "Settings": "設定", + "SettingsTabGeneral": "ユーザインタフェース", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする", + "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する", + "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する", + "SettingsTabGeneralHideCursor": "マウスカーソルを非表示", + "SettingsTabGeneralHideCursorNever": "決して", + "SettingsTabGeneralHideCursorOnIdle": "アイドル時", + "SettingsTabGeneralHideCursorAlways": "常時", + "SettingsTabGeneralGameDirectories": "ゲームディレクトリ", + "SettingsTabGeneralAdd": "追加", + "SettingsTabGeneralRemove": "削除", + "SettingsTabSystem": "システム", + "SettingsTabSystemCore": "コア", + "SettingsTabSystemSystemRegion": "地域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "アメリカ", + "SettingsTabSystemSystemRegionEurope": "ヨーロッパ", + "SettingsTabSystemSystemRegionAustralia": "オーストラリア", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韓国", + "SettingsTabSystemSystemRegionTaiwan": "台湾", + "SettingsTabSystemSystemLanguage": "言語:", + "SettingsTabSystemSystemLanguageJapanese": "日本語", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英語(アメリカ)", + "SettingsTabSystemSystemLanguageFrench": "フランス語", + "SettingsTabSystemSystemLanguageGerman": "ドイツ語", + "SettingsTabSystemSystemLanguageItalian": "イタリア語", + "SettingsTabSystemSystemLanguageSpanish": "スペイン語", + "SettingsTabSystemSystemLanguageChinese": "中国語", + "SettingsTabSystemSystemLanguageKorean": "韓国語", + "SettingsTabSystemSystemLanguageDutch": "オランダ語", + "SettingsTabSystemSystemLanguagePortuguese": "ポルトガル語", + "SettingsTabSystemSystemLanguageRussian": "ロシア語", + "SettingsTabSystemSystemLanguageTaiwanese": "台湾語", + "SettingsTabSystemSystemLanguageBritishEnglish": "英語(イギリス)", + "SettingsTabSystemSystemLanguageCanadianFrench": "フランス語(カナダ)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "スペイン語(ラテンアメリカ)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "中国語", + "SettingsTabSystemSystemLanguageTraditionalChinese": "台湾語", + "SettingsTabSystemSystemTimeZone": "タイムゾーン:", + "SettingsTabSystemSystemTime": "時刻:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック", + "SettingsTabSystemAudioBackend": "音声バックエンド:", + "SettingsTabSystemAudioBackendDummy": "ダミー", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "ハック", + "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", + "SettingsTabSystemExpandDramSize": "DRAMサイズを6GiBに拡大する", + "SettingsTabSystemIgnoreMissingServices": "未実装サービスを無視する", + "SettingsTabGraphics": "グラフィックス", + "SettingsTabGraphicsAPI": "グラフィックスAPI", + "SettingsTabGraphicsEnableShaderCache": "シェーダーキャッシュを有効にする", + "SettingsTabGraphicsAnisotropicFiltering": "異方性フィルタリング:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "解像度:", + "SettingsTabGraphicsResolutionScaleCustom": "カスタム (非推奨)", + "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "アスペクト比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ウインドウサイズに合わせる", + "SettingsTabGraphicsDeveloperOptions": "開発者向けオプション", + "SettingsTabGraphicsShaderDumpPath": "グラフィックス シェーダー ダンプパス:", + "SettingsTabLogging": "ロギング", + "SettingsTabLoggingLogging": "ロギング", + "SettingsTabLoggingEnableLoggingToFile": "ファイルへのロギングを有効にする", + "SettingsTabLoggingEnableStubLogs": "Stub ログを有効にする", + "SettingsTabLoggingEnableInfoLogs": "Info ログを有効にする", + "SettingsTabLoggingEnableWarningLogs": "Warning ログを有効にする", + "SettingsTabLoggingEnableErrorLogs": "Error ログを有効にする", + "SettingsTabLoggingEnableTraceLogs": "Trace ログを有効にする", + "SettingsTabLoggingEnableGuestLogs": "Guest ログを有効にする", + "SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効にする", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:", + "SettingsTabLoggingDeveloperOptions": "開発者オプション", + "SettingsTabLoggingDeveloperOptionsNote": "警告: パフォーマンスを低下させます", + "SettingsTabLoggingGraphicsBackendLogLevel": "グラフィックスバックエンド ログレベル:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "なし", + "SettingsTabLoggingGraphicsBackendLogLevelError": "エラー", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて", + "SettingsTabLoggingEnableDebugLogs": "デバッグログを有効にする", + "SettingsTabInput": "入力", + "SettingsTabInputEnableDockedMode": "ドッキングモード", + "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", + "SettingsButtonSave": "セーブ", + "SettingsButtonClose": "閉じる", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "キャンセル", + "SettingsButtonApply": "適用", + "ControllerSettingsPlayer": "プレイヤー", + "ControllerSettingsPlayer1": "プレイヤー 1", + "ControllerSettingsPlayer2": "プレイヤー 2", + "ControllerSettingsPlayer3": "プレイヤー 3", + "ControllerSettingsPlayer4": "プレイヤー 4", + "ControllerSettingsPlayer5": "プレイヤー 5", + "ControllerSettingsPlayer6": "プレイヤー 6", + "ControllerSettingsPlayer7": "プレイヤー 7", + "ControllerSettingsPlayer8": "プレイヤー 8", + "ControllerSettingsHandheld": "携帯", + "ControllerSettingsInputDevice": "入力デバイス", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "無効", + "ControllerSettingsControllerType": "コントローラ種別", + "ControllerSettingsControllerTypeHandheld": "携帯", + "ControllerSettingsControllerTypeProController": "Pro コントローラ", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon ペア", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 左", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon 右", + "ControllerSettingsProfile": "プロファイル", + "ControllerSettingsProfileDefault": "デフォルト", + "ControllerSettingsLoad": "ロード", + "ControllerSettingsAdd": "追加", + "ControllerSettingsRemove": "削除", + "ControllerSettingsButtons": "ボタン", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "十字キー", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "ボタン", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "スティック", + "ControllerSettingsStickInvertXAxis": "X軸を反転", + "ControllerSettingsStickInvertYAxis": "Y軸を反転", + "ControllerSettingsStickDeadzone": "遊び:", + "ControllerSettingsLStick": "左スティック", + "ControllerSettingsRStick": "右スティック", + "ControllerSettingsTriggersLeft": "左トリガー", + "ControllerSettingsTriggersRight": "右トリガー", + "ControllerSettingsTriggersButtonsLeft": "左トリガーボタン", + "ControllerSettingsTriggersButtonsRight": "右トリガーボタン", + "ControllerSettingsTriggers": "トリガー", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左ボタン", + "ControllerSettingsExtraButtonsRight": "右ボタン", + "ControllerSettingsMisc": "その他", + "ControllerSettingsTriggerThreshold": "トリガーしきい値:", + "ControllerSettingsMotion": "モーション", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 互換モーションを使用", + "ControllerSettingsMotionControllerSlot": "コントローラ スロット:", + "ControllerSettingsMotionMirrorInput": "入力反転", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon 右 スロット:", + "ControllerSettingsMotionServerHost": "サーバ:", + "ControllerSettingsMotionGyroSensitivity": "ジャイロ感度:", + "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", + "ControllerSettingsSave": "セーブ", + "ControllerSettingsClose": "閉じる", + "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", + "UserProfilesSaveProfileName": "プロファイル名をセーブ", + "UserProfilesChangeProfileImage": "プロファイル画像を変更", + "UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:", + "UserProfilesAddNewProfile": "プロファイルを作成", + "UserProfilesDelete": "削除", + "UserProfilesClose": "閉じる", + "ProfileNameSelectionWatermark": "ニックネームを選択", + "ProfileImageSelectionTitle": "プロファイル画像選択", + "ProfileImageSelectionHeader": "プロファイル画像を選択", + "ProfileImageSelectionNote": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます", + "ProfileImageSelectionImportImage": "画像ファイルをインポート", + "ProfileImageSelectionSelectAvatar": "ファームウェア内のアバターを選択", + "InputDialogTitle": "入力ダイアログ", + "InputDialogOk": "OK", + "InputDialogCancel": "キャンセル", + "InputDialogAddNewProfileTitle": "プロファイル名を選択", + "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", + "InputDialogAddNewProfileSubtext": "(最大長: {0})", + "AvatarChoose": "選択", + "AvatarSetBackgroundColor": "背景色を指定", + "AvatarClose": "閉じる", + "ControllerSettingsLoadProfileToolTip": "プロファイルをロード", + "ControllerSettingsAddProfileToolTip": "プロファイルを追加", + "ControllerSettingsRemoveProfileToolTip": "プロファイルを削除", + "ControllerSettingsSaveProfileToolTip": "プロファイルをセーブ", + "MenuBarFileToolsTakeScreenshot": "スクリーンショットを撮影", + "MenuBarFileToolsHideUi": "UIを隠す", + "GameListContextMenuRunApplication": "アプリケーションを実行", + "GameListContextMenuToggleFavorite": "お気に入りを切り替え", + "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", + "SettingsTabGeneralTheme": "テーマ", + "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", + "SettingsTabGeneralThemeBaseStyle": "基本スタイル", + "SettingsTabGeneralThemeBaseStyleDark": "ダーク", + "SettingsTabGeneralThemeBaseStyleLight": "ライト", + "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効にする", + "ButtonBrowse": "参照", + "ControllerSettingsConfigureGeneral": "設定", + "ControllerSettingsRumble": "振動", + "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", + "ControllerSettingsRumbleWeakMultiplier": "弱振動の補正値", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] のセーブデータはありません", + "DialogMessageSaveNotAvailableCreateSaveMessage": "このゲームのセーブデータを作成してよろしいですか?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - アップデータ", + "DialogErrorTitle": "Ryujinx - エラー", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 終了", + "DialogErrorMessage": "エラーが発生しました", + "DialogExitMessage": "Ryujinx を閉じてよろしいですか?", + "DialogExitSubMessage": "セーブされていないデータはすべて失われます!", + "DialogMessageCreateSaveErrorMessage": "セーブデータ: {0} の作成中にエラーが発生しました", + "DialogMessageFindSaveErrorMessage": "セーブデータ: {0} の検索中にエラーが発生しました", + "FolderDialogExtractTitle": "展開フォルダを選択", + "DialogNcaExtractionMessage": "{1} から {0} セクションを展開中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA セクション展開", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "展開に失敗しました. 選択されたファイルにはメイン NCA が存在しません.", + "DialogNcaExtractionCheckLogErrorMessage": "展開に失敗しました. 詳細はログを確認してください.", + "DialogNcaExtractionSuccessMessage": "展開が正常終了しました", + "DialogUpdaterConvertFailedMessage": "現在の Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterCancelUpdateMessage": "アップデータをキャンセル中!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "最新バージョンの Ryujinx を使用中です!", + "DialogUpdaterFailedToGetVersionMessage": "Github からのリリース情報取得時にエラーが発生しました. Github Actions でリリースファイルを作成中かもしれません. 後ほどもう一度試してみてください.", + "DialogUpdaterConvertFailedGithubMessage": "Github から取得した Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterDownloadingMessage": "アップデートをダウンロード中...", + "DialogUpdaterExtractionMessage": "アップデートを展開中...", + "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", + "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", + "DialogUpdaterCompleteMessage": "アップデート完了!", + "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", + "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", + "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", + "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", + "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", + "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", + "DialogUpdaterDirtyBuildSubMessage": "サポートされているバージョンをお探しなら, https://ryujinx.org/ で Ryujinx をダウンロードしてください.", + "DialogRestartRequiredMessage": "再起動が必要", + "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", + "DialogThemeRestartSubMessage": "再起動しますか", + "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", + "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", + "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", + "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", + "DialogInstallFileTypesErrorMessage": "ファイル形式のインストールに失敗しました.", + "DialogUninstallFileTypesSuccessMessage": "ファイル形式のアンインストールに成功しました!", + "DialogUninstallFileTypesErrorMessage": "ファイル形式のアンインストールに失敗しました.", + "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", + "DialogControllerAppletTitle": "コントローラアプレット", + "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", + "DialogErrorAppletErrorExceptionMessage": "エラーアプレットダイアログ表示エラー: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nこのエラーへの対処方法については, セットアップガイドを参照してください.", + "DialogUserErrorDialogTitle": "Ryujinx エラー ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API からの情報取得中にエラーが発生しました.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.", + "DialogProfileInvalidProfileErrorMessage": "プロファイル {0} は現在の入力設定システムと互換性がありません.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "デフォルトのプロファイルは上書きできません", + "DialogProfileDeleteProfileTitle": "プロファイルを削除中", + "DialogProfileDeleteProfileMessage": "このアクションは元に戻せません. 本当に続けてよろしいですか?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?", + "DialogPPTCDeletionErrorMessage": "PPTC キャッシュ破棄エラー {0}: {1}", + "DialogShaderDeletionMessage": "シェーダーキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?", + "DialogShaderDeletionErrorMessage": "シェーダーキャッシュ破棄エラー {0}: {1}", + "DialogRyujinxErrorMessage": "エラーが発生しました", + "DialogInvalidTitleIdErrorMessage": "UI エラー: 選択されたゲームは有効なタイトル ID を保持していません", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} には有効なシステムファームウェアがありません.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ファームウェア {0} をインストール", + "DialogFirmwareInstallerFirmwareInstallMessage": "システムバージョン {0} がインストールされます.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n現在のシステムバージョン {0} を置き換えます.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n続けてよろしいですか?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "ファームウェアをインストール中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "システムバージョン {0} が正常にインストールされました.", + "DialogUserProfileDeletionWarningMessage": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります", + "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", + "DialogUserProfileUnsavedChangesTitle": "警告 - 保存されていない変更", + "DialogUserProfileUnsavedChangesMessage": "保存されていないユーザプロファイルを変更しました.", + "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", + "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", + "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", + "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", + "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", + "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", + "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", + "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "SettingsTabGraphicsFeaturesOptions": "機能", + "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", + "CommonAuto": "自動", + "CommonOff": "オフ", + "CommonOn": "オン", + "InputDialogYes": "はい", + "InputDialogNo": "いいえ", + "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", + "MenuBarOptionsPauseEmulation": "中断", + "MenuBarOptionsResumeEmulation": "再開", + "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", + "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", + "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", + "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", + "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", + "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", + "AboutRyujinxAboutTitle": "Ryujinx について:", + "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", + "AboutRyujinxMaintainersTitle": "開発者:", + "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", + "AboutRyujinxSupprtersTitle": "Patreon での支援者:", + "AmiiboSeriesLabel": "Amiibo シリーズ", + "AmiiboCharacterLabel": "キャラクタ", + "AmiiboScanButtonLabel": "スキャン", + "AmiiboOptionsShowAllLabel": "すべての Amiibo を表示", + "AmiiboOptionsUsRandomTagLabel": "ハック: ランダムな Uuid を使用", + "DlcManagerTableHeadingEnabledLabel": "有効", + "DlcManagerTableHeadingTitleIdLabel": "タイトルID", + "DlcManagerTableHeadingContainerPathLabel": "コンテナパス", + "DlcManagerTableHeadingFullPathLabel": "フルパス", + "DlcManagerRemoveAllButton": "すべて削除", + "DlcManagerEnableAllButton": "すべて有効", + "DlcManagerDisableAllButton": "すべて無効", + "MenuBarOptionsChangeLanguage": "言語を変更", + "MenuBarShowFileTypes": "ファイル形式を表示", + "CommonSort": "並べ替え", + "CommonShowNames": "名称を表示", + "CommonFavorite": "お気に入り", + "OrderAscending": "昇順", + "OrderDescending": "降順", + "SettingsTabGraphicsFeatures": "機能", + "ErrorWindowTitle": "エラーウインドウ", + "ToggleDiscordTooltip": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します", + "AddGameDirBoxTooltip": "リストに追加するゲームディレクトリを入力します", + "AddGameDirTooltip": "リストにゲームディレクトリを追加します", + "RemoveGameDirTooltip": "選択したゲームディレクトリを削除します", + "CustomThemeCheckTooltip": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します", + "CustomThemePathTooltip": "カスタム GUI テーマのパスです", + "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", + "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", + "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", + "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", + "RegionTooltip": "システムの地域を変更します", + "LanguageTooltip": "システムの言語を変更します", + "TimezoneTooltip": "システムのタイムゾーンを変更します", + "TimeTooltip": "システムの時刻を変更します", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", + "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", + "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", + "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", + "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", + "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", + "MemoryManagerUnsafeTooltip": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.", + "UseHypervisorTooltip": "JIT の代わりにハイパーバイザーを使用します. 利用可能な場合, パフォーマンスが大幅に向上しますが, 現在の状態では不安定になる可能性があります.", + "DRamTooltip": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.", + "IgnoreMissingServicesTooltip": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.", + "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", + "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", + "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", + "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", + "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", + "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", + "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "InfoLogTooltip": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "WarnLogTooltip": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "ErrorLogTooltip": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "TraceLogTooltip": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "GuestLogTooltip": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "FileAccessLogTooltip": "ファイルアクセスログメッセージをコンソールに出力します.", + "FSAccessLogModeTooltip": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です", + "DeveloperOptionTooltip": "使用上の注意", + "OpenGlLogLevel": "適切なログレベルを有効にする必要があります", + "DebugLogTooltip": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", + "LoadApplicationFileTooltip": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます", + "LoadApplicationFolderTooltip": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます", + "OpenRyujinxFolderTooltip": "Ryujinx ファイルシステムフォルダを開きます", + "OpenRyujinxLogsTooltip": "ログが格納されるフォルダを開きます", + "ExitTooltip": "Ryujinx を終了します", + "OpenSettingsTooltip": "設定ウインドウを開きます", + "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", + "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", + "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", + "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", + "GridSize": "グリッドサイズ", + "GridSizeTooltip": "グリッドサイズを変更します", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "ポルトガル語(ブラジル)", + "AboutRyujinxContributorsButtonHeader": "すべての貢献者を確認", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "音量を変更します", + "SettingsTabSystemEnableInternetAccess": "ゲストインターネットアクセス / LAN モード", + "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", + "GameListContextMenuManageCheatToolTip": "チートを管理します", + "GameListContextMenuManageCheat": "チートを管理", + "ControllerSettingsStickRange": "範囲:", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", + "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音声", + "SettingsTabNetwork": "ネットワーク", + "SettingsTabNetworkConnection": "ネットワーク接続", + "SettingsTabCpuCache": "CPU キャッシュ", + "SettingsTabCpuMemory": "CPU メモリ", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", + "UpdaterDisabledWarningTitle": "アップデータは無効です!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", + "ControllerSettingsRotate90": "時計回りに 90° 回転", + "IconSize": "アイコンサイズ", + "IconSizeTooltip": "ゲームアイコンのサイズを変更します", + "MenuBarOptionsShowConsole": "コンソールを表示", + "ShaderCachePurgeError": "シェーダーキャッシュの破棄エラー {0}: {1}", + "UserErrorNoKeys": "Keys がありません", + "UserErrorNoFirmware": "ファームウェアがありません", + "UserErrorFirmwareParsingFailed": "ファームウェアのパーズエラー", + "UserErrorApplicationNotFound": "アプリケーションがありません", + "UserErrorUnknown": "不明なエラー", + "UserErrorUndefined": "未定義エラー", + "UserErrorNoKeysDescription": "'prod.keys' が見つかりませんでした", + "UserErrorNoFirmwareDescription": "インストールされたファームウェアが見つかりませんでした", + "UserErrorFirmwareParsingFailedDescription": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.", + "UserErrorApplicationNotFoundDescription": "指定されたパスに有効なアプリケーションがありませんでした.", + "UserErrorUnknownDescription": "不明なエラーが発生しました!", + "UserErrorUndefinedDescription": "未定義のエラーが発生しました! 発生すべきものではないので,開発者にご連絡ください!", + "OpenSetupGuideMessage": "セットアップガイドを開く", + "NoUpdate": "アップデートなし", + "TitleUpdateVersionLabel": "バージョン {0} - {1}", + "RyujinxInfo": "Ryujinx - 情報", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "すべての種別", + "Never": "決して", + "SwkbdMinCharacters": "最低 {0} 文字必要です", + "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", + "SoftwareKeyboard": "ソフトウェアキーボード", + "SoftwareKeyboardModeNumbersOnly": "数字のみ", + "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", + "SoftwareKeyboardModeASCII": "ASCII文字列のみ", + "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", + "UpdaterRenaming": "古いファイルをリネーム中...", + "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", + "UpdaterAddingFiles": "新規ファイルを追加中...", + "UpdaterExtracting": "アップデートを展開中...", + "UpdaterDownloading": "アップデートをダウンロード中...", + "Game": "ゲーム", + "Docked": "ドッキング", + "Handheld": "携帯", + "ConnectionError": "接続エラー.", + "AboutPageDeveloperListMore": "{0}, その他大勢...", + "ApiError": "API エラー.", + "LoadingHeading": "ロード中: {0}", + "CompilingPPTC": "PTC をコンパイル中", + "CompilingShaders": "シェーダーをコンパイル中", + "AllKeyboards": "すべてのキーボード", + "OpenFileDialogTitle": "開くファイルを選択", + "OpenFolderDialogTitle": "展開されたゲームフォルダを選択", + "AllSupportedFormats": "すべての対応フォーマット", + "RyujinxUpdater": "Ryujinx アップデータ", + "SettingsTabHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", + "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", + "SettingsTabHotkeysShowUiHotkey": "UI表示:", + "SettingsTabHotkeysPauseHotkey": "中断:", + "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", + "ControllerMotionTitle": "モーションコントロール設定", + "ControllerRumbleTitle": "振動設定", + "SettingsSelectThemeFileDialogTitle": "テーマファイルを選択", + "SettingsXamlThemeFile": "Xaml テーマファイル", + "AvatarWindowTitle": "アカウント - アバター管理", + "Amiibo": "Amiibo", + "Unknown": "不明", + "Usage": "使用法", + "Writable": "書き込み可能", + "SelectDlcDialogTitle": "DLC ファイルを選択", + "SelectUpdateDialogTitle": "アップデートファイルを選択", + "UserProfileWindowTitle": "ユーザプロファイルを管理", + "CheatWindowTitle": "チート管理", + "DlcWindowTitle": "DLC 管理", + "UpdateWindowTitle": "アップデート管理", + "CheatWindowHeading": "利用可能なチート {0} [{1}]", + "BuildId": "ビルドID:", + "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "UserProfilesEditProfile": "編集", + "Cancel": "キャンセル", + "Save": "セーブ", + "Discard": "破棄", + "UserProfilesSetProfileImage": "プロファイル画像を設定", + "UserProfileEmptyNameError": "名称が必要です", + "UserProfileNoImageError": "プロファイル画像が必要です", + "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", + "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "ユーザID:", + "SettingsTabGraphicsBackend": "グラフィックスバックエンド", + "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", + "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", + "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", + "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", + "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", + "SettingsGpuBackendRestartMessage": "グラフィックスバックエンドまたはGPUの設定が変更されました. 変更を適用するには再起動する必要があります", + "SettingsGpuBackendRestartSubMessage": "今すぐ再起動しますか?", + "RyujinxUpdaterMessage": "Ryujinx を最新版にアップデートしますか?", + "SettingsTabHotkeysVolumeUpHotkey": "音量を上げる:", + "SettingsTabHotkeysVolumeDownHotkey": "音量を下げる:", + "SettingsEnableMacroHLE": "マクロの高レベルエミュレーション (HLE) を有効にする", + "SettingsEnableMacroHLETooltip": "GPU マクロコードの高レベルエミュレーションです.\n\nパフォーマンスを向上させますが, 一部のゲームでグラフィックに不具合が発生する可能性があります.\n\nよくわからない場合はオンのままにしてください.", + "SettingsEnableColorSpacePassthrough": "色空間をパススルー", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan バックエンドに対して, 色空間を指定せずに色情報を渡します. 高色域ディスプレイを使用する場合, 正確ではないですがより鮮やかな色になる可能性があります.", + "VolumeShort": "音量", + "UserProfilesManageSaves": "セーブデータの管理", + "DeleteUserSave": "このゲームのユーザセーブデータを削除しますか?", + "IrreversibleActionNote": "この操作は元に戻せません.", + "SaveManagerHeading": "{0} のセーブデータを管理", + "SaveManagerTitle": "セーブデータマネージャ", + "Name": "名称", + "Size": "サイズ", + "Search": "検索", + "UserProfilesRecoverLostAccounts": "アカウントの復旧", + "Recover": "復旧", + "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", + "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", + "GraphicsAATooltip": "ゲームのレンダリングにアンチエイリアスを適用します", + "GraphicsAALabel": "アンチエイリアス:", + "GraphicsScalingFilterLabel": "スケーリングフィルタ:", + "GraphicsScalingFilterTooltip": "フレームバッファスケーリングを有効にします", + "GraphicsScalingFilterLevelLabel": "レベル", + "GraphicsScalingFilterLevelTooltip": "スケーリングフィルタのレベルを設定", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "ユーザを編集", + "UserEditorTitleCreate": "ユーザを作成", + "SettingsTabNetworkInterface": "ネットワークインタフェース:", + "NetworkInterfaceTooltip": "LAN機能に使用されるネットワークインタフェース", + "NetworkInterfaceDefault": "デフォルト", + "PackagingShaders": "シェーダーを構築中", + "AboutChangelogButton": "GitHub で更新履歴を表示", + "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json new file mode 100644 index 00000000..cdc61722 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -0,0 +1,656 @@ +{ + "Language": "한국어", + "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", + "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", + "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", + "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트 확인 안함 (가장 빠르나 안전하지 않음)", + "SettingsTabSystemUseHypervisor": "하이퍼바이저 사용하기", + "MenuBarFile": "_파일", + "MenuBarFileOpenFromFile": "_파일에서 응용 프로그램 불러오기", + "MenuBarFileOpenUnpacked": "_압축을 푼 게임 불러오기", + "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", + "MenuBarFileOpenLogsFolder": "로그 폴더 열기", + "MenuBarFileExit": "_종료", + "MenuBarOptions": "옵션", + "MenuBarOptionsToggleFullscreen": "전체화면 전환", + "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", + "MenuBarOptionsStopEmulation": "에뮬레이션 중지", + "MenuBarOptionsSettings": "_설정", + "MenuBarOptionsManageUserProfiles": "_사용자 프로파일 관리", + "MenuBarActions": "_동작", + "MenuBarOptionsSimulateWakeUpMessage": "깨우기 메시지 시뮬레이션", + "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarTools": "_도구", + "MenuBarToolsInstallFirmware": "펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉터리에서 펌웨어 설치", + "MenuBarToolsManageFileTypes": "파일 형식 관리", + "MenuBarToolsInstallFileTypes": "파일 형식 설치", + "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", + "MenuBarHelp": "도움말", + "MenuBarHelpCheckForUpdates": "업데이트 확인", + "MenuBarHelpAbout": "정보", + "MenuSearch": "검색...", + "GameListHeaderFavorite": "즐겨찾기", + "GameListHeaderIcon": "아이콘", + "GameListHeaderApplication": "이름", + "GameListHeaderDeveloper": "개발자", + "GameListHeaderVersion": "버전", + "GameListHeaderTimePlayed": "플레이 시간", + "GameListHeaderLastPlayed": "마지막 플레이", + "GameListHeaderFileExtension": "파일 확장자", + "GameListHeaderFileSize": "파일 크기", + "GameListHeaderPath": "경로", + "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", + "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리", + "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", + "GameListContextMenuManageDlc": "DLC 관리", + "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", + "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "응용프로그램의 Mod가 포함된 디렉터리 열기", + "GameListContextMenuCacheManagement": "캐시 관리", + "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", + "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", + "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "응용프로그램 셰이더 캐시 삭제\n", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉터리 열기", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용프로그램 PPTC 캐시가 포함된 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용프로그램 셰이더 캐시가 포함된 디렉터리 열기", + "GameListContextMenuExtractData": "데이터 추출", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "응용프로그램의 현재 구성에서 ExeFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataLogo": "로고", + "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", + "StatusBarSystemVersion": "시스템 버전 : {0}", + "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count의 값을 {0}으로 늘리시겠습니까?", + "LinuxVmMaxMapCountDialogTextSecondary": "일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "예, 다음에 다시 시작할 때까지", + "LinuxVmMaxMapCountDialogButtonPersistent": "예, 영구적으로", + "LinuxVmMaxMapCountWarningTextPrimary": "메모리 매핑의 최대 용량이 권장 용량보다 적습니다.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count({0})의 현재 값이 {1}보다 낮습니다. 일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.\n\n수동으로 제한을 늘리거나 Ryujinx의 도움을 받을 수 있는 pkexec을 설치하는 것이 좋습니다.", + "Settings": "설정", + "SettingsTabGeneral": "사용자 인터페이스", + "SettingsTabGeneralGeneral": "일반", + "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화", + "SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인", + "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralHideCursor": "마우스 커서 숨기기", + "SettingsTabGeneralHideCursorNever": "절대 안 함", + "SettingsTabGeneralHideCursorOnIdle": "유휴 상태", + "SettingsTabGeneralHideCursorAlways": "언제나", + "SettingsTabGeneralGameDirectories": "게임 디렉터리", + "SettingsTabGeneralAdd": "추가", + "SettingsTabGeneralRemove": "제거", + "SettingsTabSystem": "시스템", + "SettingsTabSystemCore": "코어", + "SettingsTabSystemSystemRegion": "시스템 지역:", + "SettingsTabSystemSystemRegionJapan": "일본", + "SettingsTabSystemSystemRegionUSA": "미국", + "SettingsTabSystemSystemRegionEurope": "유럽", + "SettingsTabSystemSystemRegionAustralia": "호주", + "SettingsTabSystemSystemRegionChina": "중국", + "SettingsTabSystemSystemRegionKorea": "한국", + "SettingsTabSystemSystemRegionTaiwan": "대만", + "SettingsTabSystemSystemLanguage": "시스템 언어 :", + "SettingsTabSystemSystemLanguageJapanese": "일본어", + "SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)", + "SettingsTabSystemSystemLanguageFrench": "프랑스어", + "SettingsTabSystemSystemLanguageGerman": "독일어", + "SettingsTabSystemSystemLanguageItalian": "이탈리아어", + "SettingsTabSystemSystemLanguageSpanish": "스페인어", + "SettingsTabSystemSystemLanguageChinese": "중국어", + "SettingsTabSystemSystemLanguageKorean": "한국어", + "SettingsTabSystemSystemLanguageDutch": "네덜란드어", + "SettingsTabSystemSystemLanguagePortuguese": "포르투갈어", + "SettingsTabSystemSystemLanguageRussian": "러시아어", + "SettingsTabSystemSystemLanguageTaiwanese": "대만어", + "SettingsTabSystemSystemLanguageBritishEnglish": "영어(영국)", + "SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어(캐나다)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어(라틴 아메리카)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체", + "SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체", + "SettingsTabSystemSystemTimeZone": "시스템 시간대:", + "SettingsTabSystemSystemTime": "시스템 시간:", + "SettingsTabSystemEnableVsync": "수직 동기화", + "SettingsTabSystemEnablePptc": "PPTC(프로파일된 영구 번역 캐시)", + "SettingsTabSystemEnableFsIntegrityChecks": "파일 시스템 무결성 검사", + "SettingsTabSystemAudioBackend": "음향 후단부 :", + "SettingsTabSystemAudioBackendDummy": "더미", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "사운드IO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "해킹", + "SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음", + "SettingsTabSystemExpandDramSize": "대체 메모리 레이아웃 사용(개발자)", + "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", + "SettingsTabGraphics": "그래픽", + "SettingsTabGraphicsAPI": "그래픽 API", + "SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화", + "SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링 :", + "SettingsTabGraphicsAnisotropicFilteringAuto": "자동", + "SettingsTabGraphicsAnisotropicFiltering2x": "2배", + "SettingsTabGraphicsAnisotropicFiltering4x": "4배", + "SettingsTabGraphicsAnisotropicFiltering8x": "8배", + "SettingsTabGraphicsAnisotropicFiltering16x": "16배", + "SettingsTabGraphicsResolutionScale": "해상도 배율 :", + "SettingsTabGraphicsResolutionScaleCustom": "사용자 정의(권장하지 않음)", + "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4배(2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "종횡비 :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기", + "SettingsTabGraphicsDeveloperOptions": "개발자 옵션", + "SettingsTabGraphicsShaderDumpPath": "그래픽 셰이더 덤프 경로 :", + "SettingsTabLogging": "로그 기록", + "SettingsTabLoggingLogging": "로그 기록", + "SettingsTabLoggingEnableLoggingToFile": "파일에 로그 기록 활성화", + "SettingsTabLoggingEnableStubLogs": "스텁 로그 활성화", + "SettingsTabLoggingEnableInfoLogs": "정보 로그 활성화", + "SettingsTabLoggingEnableWarningLogs": "경고 로그 활성화", + "SettingsTabLoggingEnableErrorLogs": "오류 로그 활성화", + "SettingsTabLoggingEnableTraceLogs": "추적 로그 활성화", + "SettingsTabLoggingEnableGuestLogs": "게스트 로그 활성화", + "SettingsTabLoggingEnableFsAccessLogs": "Fs 접속 로그 활성화", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 접속 로그 모드 :", + "SettingsTabLoggingDeveloperOptions": "개발자 옵션", + "SettingsTabLoggingDeveloperOptionsNote": "경고: 성능이 저하됨", + "SettingsTabLoggingGraphicsBackendLogLevel": "그래픽 후단부 로그 수준 :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "없음", + "SettingsTabLoggingGraphicsBackendLogLevelError": "오류", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "느려짐", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "모두", + "SettingsTabLoggingEnableDebugLogs": "디버그 로그 활성화", + "SettingsTabInput": "입력", + "SettingsTabInputEnableDockedMode": "도킹 모드", + "SettingsTabInputDirectKeyboardAccess": "직접 키보드 접속", + "SettingsButtonSave": "저장", + "SettingsButtonClose": "닫기", + "SettingsButtonOk": "확인", + "SettingsButtonCancel": "취소", + "SettingsButtonApply": "적용", + "ControllerSettingsPlayer": "플레이어", + "ControllerSettingsPlayer1": "플레이어 1", + "ControllerSettingsPlayer2": "플레이어 2", + "ControllerSettingsPlayer3": "플레이어 3", + "ControllerSettingsPlayer4": "플레이어 4", + "ControllerSettingsPlayer5": "플레이어 5", + "ControllerSettingsPlayer6": "플레이어 6", + "ControllerSettingsPlayer7": "플레이어 7", + "ControllerSettingsPlayer8": "플레이어 8", + "ControllerSettingsHandheld": "휴대 모드", + "ControllerSettingsInputDevice": "입력 장치", + "ControllerSettingsRefresh": "새로 고침", + "ControllerSettingsDeviceDisabled": "비활성화됨", + "ControllerSettingsControllerType": "컨트롤러 유형", + "ControllerSettingsControllerTypeHandheld": "휴대 모드", + "ControllerSettingsControllerTypeProController": "프로 컨트롤러", + "ControllerSettingsControllerTypeJoyConPair": "조이콘 페어링", + "ControllerSettingsControllerTypeJoyConLeft": "좌측 조이콘", + "ControllerSettingsControllerTypeJoyConRight": "우측 조이콘", + "ControllerSettingsProfile": "프로필", + "ControllerSettingsProfileDefault": "기본", + "ControllerSettingsLoad": "불러오기", + "ControllerSettingsAdd": "추가", + "ControllerSettingsRemove": "제거", + "ControllerSettingsButtons": "버튼", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "방향 패드", + "ControllerSettingsDPadUp": "↑", + "ControllerSettingsDPadDown": "↓", + "ControllerSettingsDPadLeft": "←", + "ControllerSettingsDPadRight": "→", + "ControllerSettingsStickButton": "버튼", + "ControllerSettingsStickUp": "↑", + "ControllerSettingsStickDown": "↓", + "ControllerSettingsStickLeft": "←", + "ControllerSettingsStickRight": "→", + "ControllerSettingsStickStick": "스틱", + "ControllerSettingsStickInvertXAxis": "스틱 X 축 반전", + "ControllerSettingsStickInvertYAxis": "스틱 Y 축 반전", + "ControllerSettingsStickDeadzone": "사각지대 :", + "ControllerSettingsLStick": "좌측 스틱", + "ControllerSettingsRStick": "우측 스틱", + "ControllerSettingsTriggersLeft": "좌측 트리거", + "ControllerSettingsTriggersRight": "우측 트리거", + "ControllerSettingsTriggersButtonsLeft": "좌측 트리거 버튼", + "ControllerSettingsTriggersButtonsRight": "우측 트리거 버튼", + "ControllerSettingsTriggers": "트리거 버튼", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "좌측 버튼", + "ControllerSettingsExtraButtonsRight": "우측 버튼", + "ControllerSettingsMisc": "기타", + "ControllerSettingsTriggerThreshold": "트리거 임계값 :", + "ControllerSettingsMotion": "동작", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", + "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :", + "ControllerSettingsMotionMirrorInput": "미러 입력", + "ControllerSettingsMotionRightJoyConSlot": "우측 조이콘 슬롯 :", + "ControllerSettingsMotionServerHost": "서버 호스트 :", + "ControllerSettingsMotionGyroSensitivity": "자이로 감도 :", + "ControllerSettingsMotionGyroDeadzone": "자이로 사각지대 :", + "ControllerSettingsSave": "저장", + "ControllerSettingsClose": "닫기", + "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", + "UserProfilesSaveProfileName": "프로필 이름 저장", + "UserProfilesChangeProfileImage": "프로필 이미지 변경", + "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :", + "UserProfilesAddNewProfile": "프로필 생성", + "UserProfilesDelete": "삭제", + "UserProfilesClose": "닫기", + "ProfileNameSelectionWatermark": "닉네임을 입력하세요", + "ProfileImageSelectionTitle": "프로필 이미지 선택", + "ProfileImageSelectionHeader": "프로필 이미지 선택", + "ProfileImageSelectionNote": "사용자 지정 프로필 이미지를 가져오거나 시스템 펌웨어에서 아바타 선택 가능", + "ProfileImageSelectionImportImage": "이미지 파일 가져오기", + "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", + "InputDialogTitle": "입력 대화상자", + "InputDialogOk": "확인", + "InputDialogCancel": "취소", + "InputDialogAddNewProfileTitle": "프로필 이름 선택", + "InputDialogAddNewProfileHeader": "프로필 이름 입력", + "InputDialogAddNewProfileSubtext": "(최대 길이 : {0})", + "AvatarChoose": "선택", + "AvatarSetBackgroundColor": "배경색 설정", + "AvatarClose": "닫기", + "ControllerSettingsLoadProfileToolTip": "프로필 불러오기", + "ControllerSettingsAddProfileToolTip": "프로필 추가", + "ControllerSettingsRemoveProfileToolTip": "프로필 제거", + "ControllerSettingsSaveProfileToolTip": "프로필 저장", + "MenuBarFileToolsTakeScreenshot": "스크린 샷 찍기", + "MenuBarFileToolsHideUi": "UI 숨기기", + "GameListContextMenuRunApplication": "응용프로그램 실행", + "GameListContextMenuToggleFavorite": "즐겨찾기 전환", + "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", + "SettingsTabGeneralTheme": "테마", + "SettingsTabGeneralThemeCustomTheme": "커스텀 테마 경로", + "SettingsTabGeneralThemeBaseStyle": "기본 스타일", + "SettingsTabGeneralThemeBaseStyleDark": "어두움", + "SettingsTabGeneralThemeBaseStyleLight": "밝음", + "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", + "ButtonBrowse": "찾아보기", + "ControllerSettingsConfigureGeneral": "구성", + "ControllerSettingsRumble": "진동", + "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", + "ControllerSettingsRumbleWeakMultiplier": "약한 진동 증폭기", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}]에 대한 저장 데이터가 없음", + "DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임에 대한 저장 데이터를 생성하겠습니까?", + "DialogConfirmationTitle": "Ryujinx - 확인", + "DialogUpdaterTitle": "Ryujinx - 업데이터", + "DialogErrorTitle": "Ryujinx - 오류", + "DialogWarningTitle": "Ryujinx - 경고", + "DialogExitTitle": "Ryujinx - 종료", + "DialogErrorMessage": "Ryujinx 오류 발생", + "DialogExitMessage": "Ryujinx를 종료하겠습니까?", + "DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!", + "DialogMessageCreateSaveErrorMessage": "지정된 저장 데이터를 작성하는 중에 오류 발생: {0}", + "DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류 발생: {0}", + "FolderDialogExtractTitle": "추출할 폴더 선택", + "DialogNcaExtractionMessage": "{1}에서 {0} 섹션을 추출하는 중...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패하였습니다. 선택한 파일에 기본 NCA가 없습니다.", + "DialogNcaExtractionCheckLogErrorMessage": "추출 실패하였습니다. 자세한 내용은 로그 파일을 읽으세요.", + "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", + "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중 입니다!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하세요.", + "DialogUpdaterConvertFailedGithubMessage": "Github 개정에서 받은 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중...", + "DialogUpdaterExtractionMessage": "업데이트 추출 중...", + "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸는 중...", + "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", + "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", + "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", + "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", + "DialogUpdaterArchNotSupportedSubMessage": "(64비트 시스템만 지원됩니다!)", + "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", + "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", + "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하세요.", + "DialogRestartRequiredMessage": "재시작 필요", + "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", + "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", + "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", + "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", + "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", + "DialogInstallFileTypesErrorMessage": "파일 형식을 설치하지 못했습니다.", + "DialogUninstallFileTypesSuccessMessage": "파일 형식을 성공적으로 제거했습니다!", + "DialogUninstallFileTypesErrorMessage": "파일 형식을 제거하지 못했습니다.", + "DialogOpenSettingsWindowLabel": "설정 창 열기", + "DialogControllerAppletTitle": "컨트롤러 애플릿", + "DialogMessageDialogErrorExceptionMessage": "메시지 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류 발생 : {0}", + "DialogErrorAppletErrorExceptionMessage": "오류에플릿 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르세요.", + "DialogUserErrorDialogTitle": "Ryuijnx 오류 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인해야 할 수 있습니다.", + "DialogProfileInvalidProfileErrorMessage": "{0} 프로필은 현재 입력 구성 시스템과 호환되지 않습니다.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없음", + "DialogProfileDeleteProfileTitle": "프로필 삭제", + "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하겠습니까?", + "DialogWarning": "경고", + "DialogPPTCDeletionMessage": "다음 부팅 시, PPTC 재구축을 대기열에 추가 :\n\n{0}\n\n계속하겠습니까?", + "DialogPPTCDeletionErrorMessage": "{0}에서 PPTC 캐시 삭제 오류 : {1}", + "DialogShaderDeletionMessage": "다음에 대한 셰이더 캐시 삭제 :\n\n{0}\n\n계속하겠습니까?", + "DialogShaderDeletionErrorMessage": "{0}에서 셰이더 캐시 제거 오류 : {1}", + "DialogRyujinxErrorMessage": "Ryujinx에 오류 발생", + "DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 타이틀 ID가 없음", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", + "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", + "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}이(가) 설치됩니다.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 시스템 버전 {0}을(를) 대체합니다.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하겠습니까?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치 중...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}이(가) 성공적으로 설치되었습니다.", + "DialogUserProfileDeletionWarningMessage": "선택한 프로파일이 삭제되면 사용 가능한 다른 프로파일이 없음", + "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", + "DialogUserProfileUnsavedChangesTitle": "경고 - 변경사항 저장되지 않음", + "DialogUserProfileUnsavedChangesMessage": "저장되지 않은 사용자 프로파일을 수정했습니다.", + "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", + "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", + "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", + "DialogLoadNcaErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", + "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "개발자만 사용하도록 설계된 셰이더 덤프를 활성화했습니다.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 세이더 덤핑을 비활성화하는 것이 좋습니다. 지금 세이더 덤핑을 비활성화하겠습니까?", + "DialogLoadAppGameAlreadyLoadedMessage": "이미 게임 불러옴", + "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으세요.", + "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", + "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", + "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "SettingsTabGraphicsFeaturesOptions": "기능", + "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", + "CommonAuto": "자동", + "CommonOff": "끔", + "CommonOn": "켬", + "InputDialogYes": "예", + "InputDialogNo": "아니오", + "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 문자가 포함되어 있습니다. 다시 시도하세요.", + "MenuBarOptionsPauseEmulation": "일시 정지", + "MenuBarOptionsResumeEmulation": "다시 시작", + "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하세요.", + "AboutDisclaimerMessage": "Ryujinx는 닌텐도™,\n또는 그 파트너와 제휴한 바가 없습니다.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", + "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", + "AboutGithubUrlTooltipMessage": "기본 브라우저에서 Ryujinx GitHub 페이지를 열려면 클릭하세요.", + "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx 디스코드 서버에 대한 초대를 열려면 클릭하세요.", + "AboutTwitterUrlTooltipMessage": "기본 브라우저에서 Ryujinx 트위터 페이지를 열려면 클릭하세요.", + "AboutRyujinxAboutTitle": "정보 :", + "AboutRyujinxAboutContent": "Ryujinx는 닌텐도 스위치™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n트위터나 디스코드에서 최신 소식을 받아보세요.\n기여에 참여하고자 하는 개발자는 GitHub 또는 디스코드에서 자세한 내용을 확인할 수 있습니다.", + "AboutRyujinxMaintainersTitle": "유지 관리 :", + "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하세요.", + "AboutRyujinxSupprtersTitle": "Patreon에서 후원:", + "AmiiboSeriesLabel": "Amiibo 시리즈", + "AmiiboCharacterLabel": "캐릭터", + "AmiiboScanButtonLabel": "스캔", + "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", + "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", + "DlcManagerTableHeadingEnabledLabel": "활성화됨", + "DlcManagerTableHeadingTitleIdLabel": "타이틀 ID", + "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", + "DlcManagerTableHeadingFullPathLabel": "전체 경로", + "DlcManagerRemoveAllButton": "모두 제거", + "DlcManagerEnableAllButton": "모두 활성화", + "DlcManagerDisableAllButton": "모두 비활성화", + "MenuBarOptionsChangeLanguage": "언어 변경", + "MenuBarShowFileTypes": "파일 유형 표시", + "CommonSort": "정렬", + "CommonShowNames": "이름 표시", + "CommonFavorite": "즐겨찾기", + "OrderAscending": "오름차순", + "OrderDescending": "내림차순", + "SettingsTabGraphicsFeatures": "기능ㆍ개선 사항", + "ErrorWindowTitle": "오류 창", + "ToggleDiscordTooltip": "\"현재 재생 중인\" 디스코드 활동에 Ryujinx를 표시할지 여부 선택", + "AddGameDirBoxTooltip": "목록에 추가할 게임 디렉터리 입력", + "AddGameDirTooltip": "목록에 게임 디렉터리 추가", + "RemoveGameDirTooltip": "선택한 게임 디렉터리 제거", + "CustomThemeCheckTooltip": "GUI에 사용자 지정 Avalonia 테마를 사용하여 에뮬레이터 메뉴의 모양 변경", + "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", + "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", + "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", + "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", + "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", + "RegionTooltip": "시스템 지역 변경", + "LanguageTooltip": "시스템 언어 변경", + "TimezoneTooltip": "시스템 시간대 변경", + "TimeTooltip": "시스템 시간 변경", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", + "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", + "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", + "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", + "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.", + "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", + "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", + "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 접속하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 접속할 수 있으므로 이 모드에서는 신뢰할 수 있는 프로그램만 실행하세요.", + "UseHypervisorTooltip": "JIT 대신 하이퍼바이저를 사용합니다. 하이퍼바이저를 사용할 수 있을 때 성능을 향상시키지만, 현재 상태에서는 불안정할 수 있습니다.", + "DRamTooltip": "대체 메모리모드 레이아웃을 활용하여 스위치 개발 모델을 모방합니다.\n\n고해상도 텍스처 팩 또는 4k 해상도 모드에만 유용합니다. 성능을 향상시키지 않습니다.\n\n확실하지 않으면 꺼 두세요.", + "IgnoreMissingServicesTooltip": "구현되지 않은 호라이즌 OS 서비스를 무시합니다. 이것은 특정 게임을 부팅할 때 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n확실하지 않으면 꺼 두세요.", + "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", + "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", + "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", + "AnisotropyTooltip": "비등방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", + "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", + "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", + "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", + "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "InfoLogTooltip": "콘솔에 정보 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "WarnLogTooltip": "콘솔에 경고 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "ErrorLogTooltip": "콘솔에 오류 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "TraceLogTooltip": "콘솔에 추적 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "GuestLogTooltip": "콘솔에 게스트 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "FileAccessLogTooltip": "콘솔에 파일 액세스 로그 메시지를 인쇄합니다.", + "FSAccessLogModeTooltip": "콘솔에 대한 FS 접속 로그 출력을 활성화합니다. 가능한 모드는 0-3\t\t\t\t", + "DeveloperOptionTooltip": "주의해서 사용", + "OpenGlLogLevel": "적절한 로그 수준을 활성화해야 함", + "DebugLogTooltip": "콘솔에 디버그 로그 메시지를 인쇄합니다.\n\n로그를 읽기 어렵게 만들고 에뮬레이터 성능을 악화시키므로 직원이 구체적으로 지시한 경우에만 사용하세요.", + "LoadApplicationFileTooltip": "파일 탐색기를 열어 불러올 스위치 호환 파일 선택", + "LoadApplicationFolderTooltip": "파일 탐색기를 열어 불러올 스위치 호환 압축 해제 응용 프로그램 선택", + "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", + "OpenRyujinxLogsTooltip": "로그가 기록된 폴더 열기", + "ExitTooltip": "Ryujinx 종료", + "OpenSettingsTooltip": "설정 창 열기", + "OpenProfileManagerTooltip": "사용자 프로파일 관리자 창 열기", + "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아감", + "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", + "OpenAboutTooltip": "정보 창 열기", + "GridSize": "격자 크기", + "GridSizeTooltip": "격자 항목의 크기 변경", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)", + "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", + "SettingsTabSystemAudioVolume": "음량 : ", + "AudioVolumeTooltip": "음향 음량 변경", + "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 접속/LAN 모드", + "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", + "GameListContextMenuManageCheatToolTip": "치트 관리", + "GameListContextMenuManageCheat": "치트 관리", + "ControllerSettingsStickRange": "범위 :", + "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", + "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "오디오", + "SettingsTabNetwork": "네트워크", + "SettingsTabNetworkConnection": "네트워크 연결", + "SettingsTabCpuCache": "CPU 캐시", + "SettingsTabCpuMemory": "CPU 모드", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", + "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", + "GameListContextMenuOpenSdModsDirectory": "분위기 모드 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "응용프로그램의 모드가 포함된 대체 SD 카드 분위기 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.", + "ControllerSettingsRotate90": "시계 방향으로 90° 회전", + "IconSize": "아이콘 크기", + "IconSizeTooltip": "게임 아이콘 크기 변경", + "MenuBarOptionsShowConsole": "콘솔 표시", + "ShaderCachePurgeError": "{0}에서 셰이더 캐시를 제거하는 중 오류 발생: {1}", + "UserErrorNoKeys": "키를 찾을 수 없음", + "UserErrorNoFirmware": "펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailed": "펌웨어 구문 분석 오류", + "UserErrorApplicationNotFound": "응용 프로그램을 찾을 수 없음", + "UserErrorUnknown": "알 수 없는 오류", + "UserErrorUndefined": "정의되지 않은 오류", + "UserErrorNoKeysDescription": "Ryujinx가 'prod.keys' 파일을 찾을 수 없음", + "UserErrorNoFirmwareDescription": "Ryujinx가 설치된 펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx가 제공된 펌웨어를 구문 분석할 수 없습니다. 일반적으로 오래된 키가 원인입니다.", + "UserErrorApplicationNotFoundDescription": "Ryujinx가 지정된 경로에서 유효한 응용 프로그램을 찾을 수 없습니다.", + "UserErrorUnknownDescription": "알 수 없는 오류가 발생했습니다!", + "UserErrorUndefinedDescription": "정의되지 않은 오류가 발생했습니다! 이런 일이 발생하면 안 되므로, 개발자에게 문의하세요!", + "OpenSetupGuideMessage": "설정 가이드 열기", + "NoUpdate": "업데이트 없음", + "TitleUpdateVersionLabel": "버전 {0}", + "RyujinxInfo": "Ryujinx - 정보", + "RyujinxConfirm": "Ryujinx - 확인", + "FileDialogAllTypes": "모든 유형", + "Never": "절대 안 함", + "SwkbdMinCharacters": "{0}자 이상이어야 함", + "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", + "SoftwareKeyboard": "소프트웨어 키보드", + "SoftwareKeyboardModeNumbersOnly": "숫자만 가능", + "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", + "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", + "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하는 {0} 명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletMessage": "응용 프로그램은 다음을 사용하는 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletDockModeSet": "독 모드가 설정되었습니다. 휴대 모드도 유효하지 않습니다.", + "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", + "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", + "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", + "UpdaterExtracting": "업데이트를 추출하는 중...", + "UpdaterDownloading": "업데이트 다운로드 중...", + "Game": "게임", + "Docked": "도킹됨", + "Handheld": "휴대용", + "ConnectionError": "연결 오류입니다.", + "AboutPageDeveloperListMore": "{0} 등...", + "ApiError": "API 오류입니다.", + "LoadingHeading": "{0} 로딩 중", + "CompilingPPTC": "PTC 컴파일 중", + "CompilingShaders": "셰이더 컴파일 중", + "AllKeyboards": "모든 키보드", + "OpenFileDialogTitle": "지원되는 파일을 선택", + "OpenFolderDialogTitle": "압축을 푼 게임이 있는 폴더 선택", + "AllSupportedFormats": "지원되는 모든 형식", + "RyujinxUpdater": "Ryujinx 업데이터", + "SettingsTabHotkeys": "키보드 단축키", + "SettingsTabHotkeysHotkeys": "키보드 단축키", + "SettingsTabHotkeysToggleVsyncHotkey": "수직 동기화 전환 :", + "SettingsTabHotkeysScreenshotHotkey": "스크린샷 :", + "SettingsTabHotkeysShowUiHotkey": "UI 표시 :", + "SettingsTabHotkeysPauseHotkey": "일시 중지 :", + "SettingsTabHotkeysToggleMuteHotkey": "음 소거 :", + "ControllerMotionTitle": "동작 제어 설정", + "ControllerRumbleTitle": "진동 설정", + "SettingsSelectThemeFileDialogTitle": "테마 파일 선택", + "SettingsXamlThemeFile": "Xaml 테마 파일", + "AvatarWindowTitle": "계정 관리 - 아바타", + "Amiibo": "Amiibo", + "Unknown": "알 수 없음", + "Usage": "사용법", + "Writable": "쓰기 가능", + "SelectDlcDialogTitle": "DLC 파일 선택", + "SelectUpdateDialogTitle": "업데이트 파일 선택", + "UserProfileWindowTitle": "사용자 프로파일 관리자", + "CheatWindowTitle": "치트 관리자", + "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", + "UpdateWindowTitle": "타이틀 업데이트 관리자", + "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", + "BuildId": "빌드ID :", + "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", + "UserProfilesEditProfile": "선택된 항목 편집", + "Cancel": "취소", + "Save": "저장", + "Discard": "삭제", + "UserProfilesSetProfileImage": "프로파일 이미지 설정", + "UserProfileEmptyNameError": "이름 필요", + "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", + "GameUpdateWindowHeading": "{0} ({1})에 대한 업데이트 관리", + "SettingsTabHotkeysResScaleUpHotkey": "해상도 증가 :", + "SettingsTabHotkeysResScaleDownHotkey": "해상도 감소 :", + "UserProfilesName": "이름 :", + "UserProfilesUserId": "사용자 ID :", + "SettingsTabGraphicsBackend": "그래픽 후단부", + "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 후단부", + "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", + "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", + "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", + "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", + "SettingsGpuBackendRestartMessage": "그래픽 후단부 또는 GPU 설정이 수정되었습니다. 적용하려면 다시 시작해야 합니다.", + "SettingsGpuBackendRestartSubMessage": "지금 다시 시작하겠습니까?", + "RyujinxUpdaterMessage": "Ryujinx를 최신 버전으로 업데이트하겠습니까?", + "SettingsTabHotkeysVolumeUpHotkey": "음량 증가 :", + "SettingsTabHotkeysVolumeDownHotkey": "음량 감소 :", + "SettingsEnableMacroHLE": "매크로 HLE 활성화", + "SettingsEnableMacroHLETooltip": "GPU 매크로 코드의 높은 수준 에뮬레이션입니다.\n\n성능이 향상되지만 일부 게임에서 그래픽 결함이 발생할 수 있습니다.\n\n확실하지 않으면 켜 두세요.", + "SettingsEnableColorSpacePassthrough": "색 공간 통과", + "SettingsEnableColorSpacePassthroughTooltip": "색 공간을 지정하지 않고 색상 정보를 전달하도록 Vulkan 후단에 지시합니다. 와이드 가멋 디스플레이를 사용하는 사용자의 경우 색 정확도가 저하되지만 더 생생한 색상을 얻을 수 있습니다.", + "VolumeShort": "음량", + "UserProfilesManageSaves": "저장 관리", + "DeleteUserSave": "이 게임에 대한 사용자 저장을 삭제하겠습니까?", + "IrreversibleActionNote": "이 작업은 되돌릴 수 없습니다.", + "SaveManagerHeading": "{0} ({1})의 저장 관리", + "SaveManagerTitle": "저장 관리자", + "Name": "이름", + "Size": "크기", + "Search": "검색", + "UserProfilesRecoverLostAccounts": "잃어버린 계정 복구", + "Recover": "복구", + "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", + "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", + "GraphicsAATooltip": "게임 렌더링에 안티 앨리어싱을 적용", + "GraphicsAALabel": "안티 앨리어싱:", + "GraphicsScalingFilterLabel": "스케일링 필터:", + "GraphicsScalingFilterTooltip": "프레임버퍼 스케일링 활성화", + "GraphicsScalingFilterLevelLabel": "수준", + "GraphicsScalingFilterLevelTooltip": "스케일링 필터 수준 설정", + "SmaaLow": "SMAA 낮음", + "SmaaMedium": "SMAA 중간", + "SmaaHigh": "SMAA 높음", + "SmaaUltra": "SMAA 울트라", + "UserEditorTitle": "사용자 수정", + "UserEditorTitleCreate": "사용자 생성", + "SettingsTabNetworkInterface": "네트워크 인터페이스:", + "NetworkInterfaceTooltip": "LAN 기능에 사용되는 네트워크 인터페이스", + "NetworkInterfaceDefault": "기본", + "PackagingShaders": "셰이더 패키징 중", + "AboutChangelogButton": "GitHub에서 변경 로그 보기", + "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json new file mode 100644 index 00000000..7edf41e8 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -0,0 +1,656 @@ +{ + "Language": "Angielski (USA)", + "MenuBarFileOpenApplet": "Otwórz Aplet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", + "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", + "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", + "SettingsTabSystemUseHypervisor": "Użyj Hiperwizora", + "MenuBarFile": "_Plik", + "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", + "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", + "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", + "MenuBarFileExit": "_Wyjdź", + "MenuBarOptions": "Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", + "MenuBarOptionsSettings": "_Ustawienia", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", + "MenuBarActions": "_Akcje", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", + "MenuBarActionsScanAmiibo": "Skanuj Amiibo", + "MenuBarTools": "_Narzędzia", + "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", + "MenuBarToolsManageFileTypes": "Zarządzaj rodzajem plików", + "MenuBarToolsInstallFileTypes": "Instalacja typów plików", + "MenuBarToolsUninstallFileTypes": "Odinstaluj rodzaje plików", + "MenuBarHelp": "Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", + "MenuBarHelpAbout": "O Aplikacji", + "MenuSearch": "Wyszukaj...", + "GameListHeaderFavorite": "Ulub", + "GameListHeaderIcon": "Ikona", + "GameListHeaderApplication": "Nazwa", + "GameListHeaderDeveloper": "Deweloper", + "GameListHeaderVersion": "Wersja", + "GameListHeaderTimePlayed": "Czas Gry", + "GameListHeaderLastPlayed": "Ostatnio Grane", + "GameListHeaderFileExtension": "Rozsz. Pliku", + "GameListHeaderFileSize": "Rozm. Pliku", + "GameListHeaderPath": "Ścieżka", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", + "GameListContextMenuManageDlc": "Zarządzaj DLC", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", + "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", + "GameListContextMenuCacheManagement": "Zarządzanie Cache", + "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", + "GameListContextMenuExtractData": "Wyodrębnij Dane", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", + "StatusBarSystemVersion": "Wersja Systemu: {0}", + "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla mapowania pamięci", + "LinuxVmMaxMapCountDialogTextPrimary": "Czy chciałbyś zwiększyć wartość vm.max_map_count do {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie, jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", + "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", + "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość mapowania pamięci jest mniejsza niż zalecana.", + "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", + "Settings": "Ustawienia", + "SettingsTabGeneral": "Interfejs Użytkownika", + "SettingsTabGeneralGeneral": "Ogólne", + "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", + "SettingsTabGeneralHideCursor": "Ukryj kursor:", + "SettingsTabGeneralHideCursorNever": "Nigdy", + "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", + "SettingsTabGeneralHideCursorAlways": "Zawsze", + "SettingsTabGeneralGameDirectories": "Katalogi Gier", + "SettingsTabGeneralAdd": "Dodaj", + "SettingsTabGeneralRemove": "Usuń", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Główne", + "SettingsTabSystemSystemRegion": "Region Systemu:", + "SettingsTabSystemSystemRegionJapan": "Japonia", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Chiny", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Tajwan", + "SettingsTabSystemSystemLanguage": "Język Systemu:", + "SettingsTabSystemSystemLanguageJapanese": "Japoński", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", + "SettingsTabSystemSystemLanguageFrench": "Francuski", + "SettingsTabSystemSystemLanguageGerman": "Niemiecki", + "SettingsTabSystemSystemLanguageItalian": "Włoski", + "SettingsTabSystemSystemLanguageSpanish": "Hiszpański", + "SettingsTabSystemSystemLanguageChinese": "Chiński", + "SettingsTabSystemSystemLanguageKorean": "Koreański", + "SettingsTabSystemSystemLanguageDutch": "Holenderski", + "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", + "SettingsTabSystemSystemLanguageRussian": "Rosyjski", + "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", + "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", + "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", + "SettingsTabSystemSystemTime": "Czas Systemu:", + "SettingsTabSystemEnableVsync": "Włącz synchronizację pionową", + "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", + "SettingsTabSystemAudioBackend": "Backend Dżwięku:", + "SettingsTabSystemAudioBackendDummy": "Atrapa", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacki", + "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", + "SettingsTabSystemExpandDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", + "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", + "SettingsTabGraphics": "Grafika", + "SettingsTabGraphicsAPI": "Graficzne API", + "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczny", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", + "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", + "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", + "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", + "SettingsTabLogging": "Logowanie", + "SettingsTabLoggingLogging": "Logowanie", + "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", + "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", + "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", + "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", + "SettingsTabLoggingEnableErrorLogs": "Włącz Logi Błędów", + "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", + "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", + "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", + "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Zredukuje wydajność", + "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", + "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", + "SettingsTabInput": "Sterowanie", + "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", + "SettingsButtonSave": "Zapisz", + "SettingsButtonClose": "Zamknij", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Anuluj", + "SettingsButtonApply": "Zastosuj", + "ControllerSettingsPlayer": "Gracz", + "ControllerSettingsPlayer1": "Gracz 1", + "ControllerSettingsPlayer2": "Gracz 2", + "ControllerSettingsPlayer3": "Gracz 3", + "ControllerSettingsPlayer4": "Gracz 4", + "ControllerSettingsPlayer5": "Gracz 5", + "ControllerSettingsPlayer6": "Gracz 6", + "ControllerSettingsPlayer7": "Gracz 7", + "ControllerSettingsPlayer8": "Gracz 8", + "ControllerSettingsHandheld": "Przenośny", + "ControllerSettingsInputDevice": "Urządzenie Wejściowe", + "ControllerSettingsRefresh": "Odśwież", + "ControllerSettingsDeviceDisabled": "Wyłączone", + "ControllerSettingsControllerType": "Typ Kontrolera", + "ControllerSettingsControllerTypeHandheld": "Przenośny", + "ControllerSettingsControllerTypeProController": "Pro Kontroler", + "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", + "ControllerSettingsControllerTypeJoyConLeft": "Lewy JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Prawy JoyCon", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Domyślny", + "ControllerSettingsLoad": "Wczytaj", + "ControllerSettingsAdd": "Dodaj", + "ControllerSettingsRemove": "Usuń", + "ControllerSettingsButtons": "Przyciski", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad Kierunkowy", + "ControllerSettingsDPadUp": "Góra", + "ControllerSettingsDPadDown": "Dół", + "ControllerSettingsDPadLeft": "Lewo", + "ControllerSettingsDPadRight": "Prawo", + "ControllerSettingsStickButton": "Przycisk", + "ControllerSettingsStickUp": "Góra ", + "ControllerSettingsStickDown": "Dół ", + "ControllerSettingsStickLeft": "Lewo", + "ControllerSettingsStickRight": "Prawo", + "ControllerSettingsStickStick": "Gałka", + "ControllerSettingsStickInvertXAxis": "Odwróć gałkę X", + "ControllerSettingsStickInvertYAxis": "Odwróć gałkę Y", + "ControllerSettingsStickDeadzone": "Martwa strefa:", + "ControllerSettingsLStick": "Lewa Gałka", + "ControllerSettingsRStick": "Prawa Gałka", + "ControllerSettingsTriggersLeft": "Lewe Triggery", + "ControllerSettingsTriggersRight": "Prawe Triggery", + "ControllerSettingsTriggersButtonsLeft": "Lewe Przyciski Triggerów", + "ControllerSettingsTriggersButtonsRight": "Prawe Przyciski Triggerów", + "ControllerSettingsTriggers": "Triggery", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Lewe Przyciski", + "ControllerSettingsExtraButtonsRight": "Prawe Przyciski", + "ControllerSettingsMisc": "Różne", + "ControllerSettingsTriggerThreshold": "Próg Triggerów:", + "ControllerSettingsMotion": "Ruch", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Użyj ruchu zgodnego z CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot Kontrolera:", + "ControllerSettingsMotionMirrorInput": "Odzwierciedlaj Sterowanie", + "ControllerSettingsMotionRightJoyConSlot": "Prawy Slot JoyCon:", + "ControllerSettingsMotionServerHost": "Host Serwera:", + "ControllerSettingsMotionGyroSensitivity": "Czułość Żyroskopu:", + "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", + "ControllerSettingsSave": "Zapisz", + "ControllerSettingsClose": "Zamknij", + "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", + "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", + "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", + "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", + "UserProfilesAddNewProfile": "Utwórz Profil", + "UserProfilesDelete": "Usuwać", + "UserProfilesClose": "Zamknij", + "ProfileNameSelectionWatermark": "Wybierz pseudonim", + "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", + "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", + "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", + "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", + "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", + "InputDialogTitle": "Okno Dialogowe Wprowadzania", + "InputDialogOk": "OK", + "InputDialogCancel": "Anuluj", + "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", + "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", + "AvatarChoose": "Wybierz", + "AvatarSetBackgroundColor": "Ustaw Kolor Tła", + "AvatarClose": "Zamknij", + "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", + "ControllerSettingsAddProfileToolTip": "Dodaj Profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", + "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", + "MenuBarFileToolsHideUi": "Ukryj UI", + "GameListContextMenuRunApplication": "Uruchom aplikację ", + "GameListContextMenuToggleFavorite": "Przełącz Ulubione", + "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", + "SettingsTabGeneralTheme": "Motyw", + "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", + "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", + "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", + "SettingsTabGeneralThemeBaseStyleLight": "Jasny", + "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", + "ButtonBrowse": "Przeglądaj", + "ControllerSettingsConfigureGeneral": "Konfiguruj", + "ControllerSettingsRumble": "Wibracje", + "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", + "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", + "DialogUpdaterTitle": "Ryujinx - Aktualizator", + "DialogErrorTitle": "Ryujinx - Błąd", + "DialogWarningTitle": "Ryujinx - Uwaga", + "DialogExitTitle": "Ryujinx - Wyjdź", + "DialogErrorMessage": "Ryujinx napotkał błąd", + "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", + "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", + "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", + "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", + "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", + "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", + "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", + "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", + "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", + "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", + "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", + "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", + "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", + "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", + "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Pobierz Ryujinx ze strony https://ryujinx.org/, jeśli szukasz obsługiwanej wersji.", + "DialogRestartRequiredMessage": "Wymagane Ponowne Uruchomienie", + "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", + "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", + "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", + "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", + "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", + "DialogInstallFileTypesErrorMessage": "Nie udało się zainstalować typów plików.", + "DialogUninstallFileTypesSuccessMessage": "Pomyślnie odinstalowano typy plików!", + "DialogUninstallFileTypesErrorMessage": "Nie udało się odinstalować typów plików.", + "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", + "DialogControllerAppletTitle": "Aplet Kontrolera", + "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", + "DialogErrorAppletErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nAby uzyskać więcej informacji o tym, jak naprawić ten błąd, zapoznaj się z naszym Przewodnikiem instalacji.", + "DialogUserErrorDialogTitle": "Błąd Ryujinxa ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Wystąpił błąd podczas pobierania informacji z API.", + "DialogAmiiboApiConnectErrorMessage": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} jest niezgodny z bieżącym systemem konfiguracji sterowania.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Profil Domyślny nie może zostać nadpisany", + "DialogProfileDeleteProfileTitle": "Usuwanie Profilu", + "DialogProfileDeleteProfileMessage": "Ta czynność jest nieodwracalna, czy na pewno chcesz kontynuować?", + "DialogWarning": "Uwaga", + "DialogPPTCDeletionMessage": "Masz zamiar umieścić w kolejce rekompilację PPTC przy następnym uruchomieniu:\n\n{0}\n\nCzy na pewno chcesz kontynuować?", + "DialogPPTCDeletionErrorMessage": "Błąd czyszczenia cache PPTC w {0}: {1}", + "DialogShaderDeletionMessage": "Zamierzasz usunąć cache Shaderów dla :\n\n{0}\n\nNa pewno chcesz kontynuować?", + "DialogShaderDeletionErrorMessage": "Błąd czyszczenia cache Shaderów w {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx napotkał błąd", + "DialogInvalidTitleIdErrorMessage": "Błąd UI: Wybrana gra nie miała prawidłowego ID tytułu", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Zainstaluj Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Wersja systemu {0} zostanie zainstalowana.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nZastąpi to obecną wersję systemu {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nCzy chcesz kontynuować?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalowanie firmware'u...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Wersja systemu {0} została pomyślnie zainstalowana.", + "DialogUserProfileDeletionWarningMessage": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty", + "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", + "DialogUserProfileUnsavedChangesTitle": "Uwaga - Niezapisane zmiany", + "DialogUserProfileUnsavedChangesMessage": "Wprowadziłeś zmiany dla tego profilu użytkownika, które nie zostały zapisane.", + "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", + "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", + "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", + "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", + "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Masz włączone zrzucanie shaderów, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie zrzucania shaderów. Czy chcesz teraz wyłączyć zrzucanie shaderów?", + "DialogLoadAppGameAlreadyLoadedMessage": "Gra została już załadowana", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Zatrzymaj emulację lub zamknij emulator przed uruchomieniem innej gry.", + "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", + "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funkcje", + "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", + "CommonAuto": "Auto", + "CommonOff": "Wyłączone", + "CommonOn": "Włączone", + "InputDialogYes": "Tak", + "InputDialogNo": "Nie", + "DialogProfileInvalidProfileNameErrorMessage": "Nazwa pliku zawiera nieprawidłowe znaki. Proszę spróbuj ponownie.", + "MenuBarOptionsPauseEmulation": "Pauza", + "MenuBarOptionsResumeEmulation": "Wznów", + "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", + "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", + "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", + "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", + "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", + "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", + "AboutRyujinxAboutTitle": "O Aplikacji:", + "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", + "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", + "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", + "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", + "AmiiboSeriesLabel": "Seria Amiibo", + "AmiiboCharacterLabel": "Postać", + "AmiiboScanButtonLabel": "Zeskanuj", + "AmiiboOptionsShowAllLabel": "Pokaż Wszystkie Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Użyj losowego UUID tagu", + "DlcManagerTableHeadingEnabledLabel": "Włączone", + "DlcManagerTableHeadingTitleIdLabel": "ID Tytułu", + "DlcManagerTableHeadingContainerPathLabel": "Ścieżka Kontenera", + "DlcManagerTableHeadingFullPathLabel": "Pełna Ścieżka", + "DlcManagerRemoveAllButton": "Usuń Wszystkie", + "DlcManagerEnableAllButton": "Włącz Wszystkie", + "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "MenuBarOptionsChangeLanguage": "Zmień Język", + "MenuBarShowFileTypes": "Pokaż typy plików", + "CommonSort": "Sortuj", + "CommonShowNames": "Pokaż Nazwy", + "CommonFavorite": "Ulubione", + "OrderAscending": "Rosnąco", + "OrderDescending": "Malejąco", + "SettingsTabGraphicsFeatures": "Funkcje i Ulepszenia", + "ErrorWindowTitle": "Okno Błędu", + "ToggleDiscordTooltip": "Wybierz, czy chcesz wyświetlać Ryujinx w swojej \"aktualnie grane\" aktywności Discord", + "AddGameDirBoxTooltip": "Wprowadź katalog gier aby dodać go do listy", + "AddGameDirTooltip": "Dodaj katalog gier do listy", + "RemoveGameDirTooltip": "Usuń wybrany katalog gier", + "CustomThemeCheckTooltip": "Użyj niestandardowego motywu Avalonia dla GUI, aby zmienić wygląd menu emulatora", + "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", + "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", + "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", + "RegionTooltip": "Zmień Region Systemu", + "LanguageTooltip": "Zmień Język Systemu", + "TimezoneTooltip": "Zmień Strefę Czasową Systemu", + "TimeTooltip": "Zmień Czas Systemu", + "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", + "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", + "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", + "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", + "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", + "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", + "MemoryManagerUnsafeTooltip": "Bezpośrednio mapuj pamięć, ale nie maskuj adresu w przestrzeni adresowej gościa przed uzyskaniem dostępu. Szybciej, ale kosztem bezpieczeństwa. Aplikacja gościa może uzyskać dostęp do pamięci z dowolnego miejsca w Ryujinx, więc w tym trybie uruchamiaj tylko programy, którym ufasz.", + "UseHypervisorTooltip": "Użyj Hiperwizora zamiast JIT. Znacznie poprawia wydajność, gdy jest dostępny, ale może być niestabilny w swoim obecnym stanie ", + "DRamTooltip": "Wykorzystuje alternatywny układ MemoryMode, aby naśladować model rozwojowy Switcha.\n\nJest to przydatne tylko w przypadku pakietów tekstur o wyższej rozdzielczości lub modów w rozdzielczości 4k. NIE poprawia wydajności.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "IgnoreMissingServicesTooltip": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", + "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", + "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", + "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", + "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", + "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", + "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", + "InfoLogTooltip": "Wyświetla komunikaty dziennika informacyjnego w konsoli. Nie wpływa na wydajność.", + "WarnLogTooltip": "Wyświetla komunikaty dziennika ostrzeżeń w konsoli. Nie wpływa na wydajność.", + "ErrorLogTooltip": "Wyświetla w konsoli komunikaty dziennika błędów. Nie wpływa na wydajność.", + "TraceLogTooltip": "Wyświetla komunikaty dziennika śledzenia w konsoli. Nie wpływa na wydajność.", + "GuestLogTooltip": "Wyświetla komunikaty dziennika gości w konsoli. Nie wpływa na wydajność.", + "FileAccessLogTooltip": "Wyświetla w konsoli komunikaty dziennika dostępu do plików.", + "FSAccessLogModeTooltip": "Włącza wyjście dziennika dostępu FS do konsoli. Możliwe tryby to 0-3", + "DeveloperOptionTooltip": "Używaj ostrożnie", + "OpenGlLogLevel": "Wymaga włączonych odpowiednich poziomów logów", + "DebugLogTooltip": "Wyświetla komunikaty dziennika debugowania w konsoli.\n\nUżywaj tego tylko na wyraźne polecenie członka załogi, ponieważ utrudni to odczytanie dzienników i pogorszy wydajność emulatora.", + "LoadApplicationFileTooltip": "Otwórz eksplorator plików, aby wybrać plik kompatybilny z Switch do wczytania", + "LoadApplicationFolderTooltip": "Otwórz eksplorator plików, aby wybrać zgodną z Switch, rozpakowaną aplikację do załadowania", + "OpenRyujinxFolderTooltip": "Otwórz folder systemu plików Ryujinx", + "OpenRyujinxLogsTooltip": "Otwiera folder, w którym zapisywane są logi", + "ExitTooltip": "Wyjdź z Ryujinx", + "OpenSettingsTooltip": "Otwórz okno ustawień", + "OpenProfileManagerTooltip": "Otwórz okno Menedżera Profili Użytkownika", + "StopEmulationTooltip": "Zatrzymaj emulację bieżącej gry i wróć do wyboru gier", + "CheckUpdatesTooltip": "Sprawdź aktualizacje Ryujinx", + "OpenAboutTooltip": "Otwórz Okno Informacje", + "GridSize": "Wielkość siatki", + "GridSizeTooltip": "Zmień rozmiar elementów siatki", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazylijski Portugalski", + "AboutRyujinxContributorsButtonHeader": "Zobacz Wszystkich Współtwórców", + "SettingsTabSystemAudioVolume": "Głośność: ", + "AudioVolumeTooltip": "Zmień Głośność Dźwięku", + "SettingsTabSystemEnableInternetAccess": "Dostęp do Internetu Gościa/Tryb LAN", + "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", + "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", + "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "ControllerSettingsStickRange": "Zasięg:", + "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", + "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Dżwięk", + "SettingsTabNetwork": "Sieć", + "SettingsTabNetworkConnection": "Połączenie Sieciowe", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Pamięć CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", + "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", + "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", + "ControllerSettingsRotate90": "Obróć o 90° w Prawo", + "IconSize": "Rozmiar Ikon", + "IconSizeTooltip": "Zmień rozmiar ikon gry", + "MenuBarOptionsShowConsole": "Pokaż Konsolę", + "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", + "UserErrorNoKeys": "Nie znaleziono kluczy", + "UserErrorNoFirmware": "Nie znaleziono firmware'u", + "UserErrorFirmwareParsingFailed": "Błąd parsowania firmware'u", + "UserErrorApplicationNotFound": "Aplikacja nie znaleziona", + "UserErrorUnknown": "Nieznany błąd", + "UserErrorUndefined": "Niezdefiniowany błąd", + "UserErrorNoKeysDescription": "Ryujinx nie mógł znaleźć twojego pliku 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.", + "UserErrorApplicationNotFoundDescription": "Ryujinx nie mógł znaleźć prawidłowej aplikacji na podanej ścieżce.", + "UserErrorUnknownDescription": "Wystąpił nieznany błąd!", + "UserErrorUndefinedDescription": "Wystąpił niezdefiniowany błąd! To nie powinno się zdarzyć, skontaktuj się z deweloperem!", + "OpenSetupGuideMessage": "Otwórz Podręcznik Konfiguracji", + "NoUpdate": "Brak Aktualizacji", + "TitleUpdateVersionLabel": "Wersja {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Potwierdzenie", + "FileDialogAllTypes": "Wszystkie typy", + "Never": "Nigdy", + "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", + "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", + "SoftwareKeyboard": "Klawiatura Oprogramowania", + "SoftwareKeyboardModeNumbersOnly": "Mogą być tylko liczby ", + "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", + "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", + "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", + "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", + "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", + "UpdaterAddingFiles": "Dodawanie Nowych Plików...", + "UpdaterExtracting": "Wypakowywanie Aktualizacji...", + "UpdaterDownloading": "Pobieranie Aktualizacji...", + "Game": "Gra", + "Docked": "Zadokowany", + "Handheld": "Przenośny", + "ConnectionError": "Błąd Połączenia.", + "AboutPageDeveloperListMore": "{0} i więcej...", + "ApiError": "Błąd API.", + "LoadingHeading": "Wczytywanie {0}", + "CompilingPPTC": "Kompilowanie PTC", + "CompilingShaders": "Kompilowanie Shaderów", + "AllKeyboards": "Wszystkie klawiatury", + "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", + "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", + "AllSupportedFormats": "Wszystkie Obsługiwane Formaty", + "RyujinxUpdater": "Aktualizator Ryujinx", + "SettingsTabHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysToggleVsyncHotkey": "Przełącz VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Zrzut Ekranu:", + "SettingsTabHotkeysShowUiHotkey": "Pokaż UI:", + "SettingsTabHotkeysPauseHotkey": "Pauza:", + "SettingsTabHotkeysToggleMuteHotkey": "Wycisz:", + "ControllerMotionTitle": "Ustawienia Sterowania Ruchowego", + "ControllerRumbleTitle": "Ustawienia Wibracji", + "SettingsSelectThemeFileDialogTitle": "Wybierz Plik Motywu", + "SettingsXamlThemeFile": "Plik Motywu Xaml", + "AvatarWindowTitle": "Zarządzaj Kontami — Avatar", + "Amiibo": "Amiibo", + "Unknown": "Nieznane", + "Usage": "Użycie", + "Writable": "Zapisywalne", + "SelectDlcDialogTitle": "Wybierz pliki DLC", + "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "UserProfileWindowTitle": "Menedżer Profili Użytkowników", + "CheatWindowTitle": "Menedżer Kodów", + "DlcWindowTitle": "Menedżer Zawartości do Pobrania", + "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", + "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", + "BuildId": "Identyfikator wersji:", + "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "UserProfilesEditProfile": "Edytuj Zaznaczone", + "Cancel": "Anuluj", + "Save": "Zapisz", + "Discard": "Odrzuć", + "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", + "UserProfileEmptyNameError": "Nazwa jest wymagana", + "UserProfileNoImageError": "Należy ustawić obraz profilowy", + "GameUpdateWindowHeading": "{0} Aktualizacje dostępne dla {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", + "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", + "UserProfilesName": "Nazwa:", + "UserProfilesUserId": "ID Użytkownika:", + "SettingsTabGraphicsBackend": "Backend Graficzny", + "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", + "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", + "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", + "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", + "SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia", + "SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?", + "RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?", + "SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:", + "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", + "SettingsEnableMacroHLE": "Włącz Macro HLE", + "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Głoś", + "UserProfilesManageSaves": "Zarządzaj Zapisami", + "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", + "IrreversibleActionNote": "Ta czynność nie jest odwracalna.", + "SaveManagerHeading": "Zarządzaj Zapisami dla {0}", + "SaveManagerTitle": "Menedżer Zapisów", + "Name": "Nazwa", + "Size": "Rozmiar", + "Search": "Wyszukaj", + "UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta", + "Recover": "Odzyskaj", + "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", + "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", + "GraphicsAATooltip": "Stosuje antyaliasing do renderowania gry", + "GraphicsAALabel": "Antyaliasing:", + "GraphicsScalingFilterLabel": "Filtr skalowania:", + "GraphicsScalingFilterTooltip": "Włącza skalowanie bufora ramki", + "GraphicsScalingFilterLevelLabel": "Poziom", + "GraphicsScalingFilterLevelTooltip": "Ustaw poziom filtra skalowania", + "SmaaLow": "SMAA Niskie", + "SmaaMedium": "SMAA Średnie", + "SmaaHigh": "SMAA Wysokie", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edytuj użytkownika", + "UserEditorTitleCreate": "Utwórz użytkownika", + "SettingsTabNetworkInterface": "Interfejs sieci:", + "NetworkInterfaceTooltip": "Interfejs sieciowy używany do funkcji LAN", + "NetworkInterfaceDefault": "Domyślny", + "PackagingShaders": "Pakuje Shadery ", + "AboutChangelogButton": "Zobacz listę zmian na GitHubie", + "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json new file mode 100644 index 00000000..8909a84f --- /dev/null +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -0,0 +1,656 @@ +{ + "Language": "Português (BR)", + "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", + "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", + "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar Hipervisor", + "MenuBarFile": "_Arquivo", + "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", + "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", + "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", + "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", + "MenuBarFileExit": "_Sair", + "MenuBarOptions": "_Opções", + "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar jogos em tela cheia", + "MenuBarOptionsStopEmulation": "_Encerrar emulação", + "MenuBarOptionsSettings": "_Configurações", + "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", + "MenuBarActions": "_Ações", + "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", + "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarTools": "_Ferramentas", + "MenuBarToolsInstallFirmware": "_Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", + "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", + "MenuBarHelp": "A_juda", + "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", + "MenuBarHelpAbout": "_Sobre", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favorito", + "GameListHeaderIcon": "Ícone", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Desenvolvedor", + "GameListHeaderVersion": "Versão", + "GameListHeaderTimePlayed": "Tempo de jogo", + "GameListHeaderLastPlayed": "Último jogo", + "GameListHeaderFileExtension": "Extensão", + "GameListHeaderFileSize": "Tamanho", + "GameListHeaderPath": "Caminho", + "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", + "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", + "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", + "GameListContextMenuManageDlc": "Gerenciar DLCs", + "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", + "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", + "GameListContextMenuCacheManagement": "Gerenciamento de cache", + "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", + "GameListContextMenuExtractData": "Extrair dados", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "StatusBarGamesLoaded": "{0}/{1} jogos carregados", + "StatusBarSystemVersion": "Versão do firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Configurações", + "SettingsTabGeneral": "Geral", + "SettingsTabGeneralGeneral": "Geral", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Diretórios de jogo", + "SettingsTabGeneralAdd": "Adicionar", + "SettingsTabGeneralRemove": "Remover", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Região do sistema:", + "SettingsTabSystemSystemRegionJapan": "Japão", + "SettingsTabSystemSystemRegionUSA": "EUA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Austrália", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Coreia", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Idioma do sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonês", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", + "SettingsTabSystemSystemLanguageFrench": "Francês", + "SettingsTabSystemSystemLanguageGerman": "Alemão", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Espanhol", + "SettingsTabSystemSystemLanguageChinese": "Chinês", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Holandês", + "SettingsTabSystemSystemLanguagePortuguese": "Português", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", + "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", + "SettingsTabSystemSystemTime": "Hora do sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", + "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", + "SettingsTabSystemAudioBackendDummy": "Nenhuma", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pode causar instabilidade)", + "SettingsTabSystemExpandDramSize": "Expandir memória para 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API gráfica", + "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Escala de resolução:", + "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Proporção:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", + "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", + "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", + "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", + "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", + "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", + "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", + "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", + "SettingsTabLoggingDeveloperOptionsNote": "AVISO: Reduzirá o desempenho", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nível de log do backend gráfico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erro", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos", + "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", + "SettingsTabInput": "Controle", + "SettingsTabInputEnableDockedMode": "Habilitar modo TV", + "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", + "SettingsButtonSave": "Salvar", + "SettingsButtonClose": "Fechar", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jogador", + "ControllerSettingsPlayer1": "Jogador 1", + "ControllerSettingsPlayer2": "Jogador 2", + "ControllerSettingsPlayer3": "Jogador 3", + "ControllerSettingsPlayer4": "Jogador 4", + "ControllerSettingsPlayer5": "Jogador 5", + "ControllerSettingsPlayer6": "Jogador 6", + "ControllerSettingsPlayer7": "Jogador 7", + "ControllerSettingsPlayer8": "Jogador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Atualizar", + "ControllerSettingsDeviceDisabled": "Desabilitado", + "ControllerSettingsControllerType": "Tipo do controle", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Padrão", + "ControllerSettingsLoad": "Carregar", + "ControllerSettingsAdd": "Adicionar", + "ControllerSettingsRemove": "Remover", + "ControllerSettingsButtons": "Botões", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Direcional", + "ControllerSettingsDPadUp": "Cima", + "ControllerSettingsDPadDown": "Baixo", + "ControllerSettingsDPadLeft": "Esquerda", + "ControllerSettingsDPadRight": "Direita", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Analógico esquerdo", + "ControllerSettingsRStick": "Analógico direito", + "ControllerSettingsTriggersLeft": "Gatilhos esquerda", + "ControllerSettingsTriggersRight": "Gatilhos direita", + "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", + "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", + "ControllerSettingsTriggers": "Gatilhos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botões esquerda", + "ControllerSettingsExtraButtonsRight": "Botões direita", + "ControllerSettingsMisc": "Miscelâneas", + "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", + "ControllerSettingsMotion": "Sensor de movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot do controle:", + "ControllerSettingsMotionMirrorInput": "Espelhar movimento", + "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", + "ControllerSettingsMotionServerHost": "Endereço do servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", + "ControllerSettingsSave": "Salvar", + "ControllerSettingsClose": "Fechar", + "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", + "UserProfilesSaveProfileName": "Salvar nome de perfil", + "UserProfilesChangeProfileImage": "Mudar imagem de perfil", + "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", + "UserProfilesAddNewProfile": "Adicionar novo perfil", + "UserProfilesDelete": "Apagar", + "UserProfilesClose": "Fechar", + "ProfileNameSelectionWatermark": "Escolha um apelido", + "ProfileImageSelectionTitle": "Seleção da imagem de perfil", + "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", + "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", + "ProfileImageSelectionImportImage": "Importar arquivo de imagem", + "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", + "InputDialogTitle": "Diálogo de texto", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", + "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escolher", + "AvatarSetBackgroundColor": "Definir cor de fundo", + "AvatarClose": "Fechar", + "ControllerSettingsLoadProfileToolTip": "Carregar perfil", + "ControllerSettingsAddProfileToolTip": "Adicionar perfil", + "ControllerSettingsRemoveProfileToolTip": "Remover perfil", + "ControllerSettingsSaveProfileToolTip": "Salvar perfil", + "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", + "MenuBarFileToolsHideUi": "Esconder Interface", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Alternar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Escuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", + "ButtonBrowse": "Procurar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibração", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", + "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", + "DialogConfirmationTitle": "Ryujinx - Confirmação", + "DialogUpdaterTitle": "Ryujinx - Atualizador", + "DialogErrorTitle": "Ryujinx - Erro", + "DialogWarningTitle": "Ryujinx - Alerta", + "DialogExitTitle": "Ryujinx - Sair", + "DialogErrorMessage": "Ryujinx encontrou um erro", + "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", + "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", + "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", + "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", + "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", + "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", + "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", + "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter as informações de atualização do GitHub Release. Isso pode ser causado se uma nova versão estiver sendo compilado pelas Ações do GitHub. Tente novamente em alguns minutos.", + "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", + "DialogUpdaterDownloadingMessage": "Baixando atualização...", + "DialogUpdaterExtractionMessage": "Extraindo atualização...", + "DialogUpdaterRenamingMessage": "Renomeando atualização...", + "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", + "DialogUpdaterCompleteMessage": "Atualização concluída!", + "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", + "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", + "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", + "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", + "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", + "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", + "DialogRestartRequiredMessage": "Reinicialização necessária", + "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", + "DialogThemeRestartSubMessage": "Deseja reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", + "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", + "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", + "DialogInstallFileTypesErrorMessage": "Falha ao instalar tipos de arquivo.", + "DialogUninstallFileTypesSuccessMessage": "Tipos de arquivo desinstalados com sucesso!", + "DialogUninstallFileTypesErrorMessage": "Falha ao desinstalar tipos de arquivo.", + "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogControllerAppletTitle": "Applet de controle", + "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", + "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", + "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", + "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", + "DialogProfileDeleteProfileTitle": "Apagando perfil", + "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", + "DialogWarning": "Alerta", + "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", + "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", + "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", + "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", + "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", + "DialogUserProfileUnsavedChangesTitle": "Alerta - Alterações não salvas", + "DialogUserProfileUnsavedChangesMessage": "Você fez alterações para este perfil de usuário que não foram salvas.", + "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", + "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", + "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", + "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", + "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Recursos", + "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", + "CommonAuto": "Automático", + "CommonOff": "Desligado", + "CommonOn": "Ligado", + "InputDialogYes": "Sim", + "InputDialogNo": "Não", + "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Resumir", + "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", + "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", + "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", + "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", + "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", + "AboutRyujinxAboutTitle": "Sobre:", + "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", + "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", + "AmiiboSeriesLabel": "Franquia Amiibo", + "AmiiboCharacterLabel": "Personagem", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID do título", + "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", + "DlcManagerTableHeadingFullPathLabel": "Caminho completo", + "DlcManagerRemoveAllButton": "Remover todos", + "DlcManagerEnableAllButton": "Habilitar todos", + "DlcManagerDisableAllButton": "Desabilitar todos", + "MenuBarOptionsChangeLanguage": "Mudar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de arquivo", + "CommonSort": "Ordenar", + "CommonShowNames": "Exibir nomes", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Recursos & Melhorias", + "ErrorWindowTitle": "Janela de erro", + "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", + "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", + "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", + "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", + "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", + "CustomThemePathTooltip": "Diretório do tema customizado", + "CustomThemeBrowseTooltip": "Navegar até um tema customizado", + "DockModeToggleTooltip": "Habilita ou desabilita modo TV", + "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", + "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", + "RegionTooltip": "Mudar a região do sistema", + "LanguageTooltip": "Mudar o idioma do sistema", + "TimezoneTooltip": "Mudar o fuso-horário do sistema", + "TimeTooltip": "Mudar a hora do sistema", + "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", + "PptcToggleTooltip": "Habilita ou desabilita PPTC", + "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", + "AudioBackendTooltip": "Mudar biblioteca de áudio", + "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", + "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", + "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", + "UseHypervisorTooltip": "Usa o Hypervisor em vez de JIT (recompilador dinâmico). Melhora significativamente o desempenho quando disponível, mas pode ser instável no seu estado atual.", + "DRamTooltip": "Expande a memória do sistema emulado de 4GiB para 6GiB", + "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", + "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", + "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", + "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", + "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", + "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", + "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", + "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", + "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", + "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", + "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", + "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", + "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", + "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", + "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", + "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", + "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", + "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", + "DeveloperOptionTooltip": "Use com cuidado", + "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", + "DebugLogTooltip": "Habilita exibição de mensagens de depuração", + "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", + "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", + "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", + "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", + "ExitTooltip": "Sair do Ryujinx", + "OpenSettingsTooltip": "Abrir janela de configurações", + "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", + "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", + "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", + "OpenAboutTooltip": "Abrir janela sobre", + "GridSize": "Tamanho da grade", + "GridSizeTooltip": "Mudar tamanho dos items da grade", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", + "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", + "SettingsTabSystemAudioVolume": "Volume:", + "AudioVolumeTooltip": "Mudar volume do áudio", + "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", + "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", + "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", + "GameListContextMenuManageCheat": "Gerenciar Cheats", + "ControllerSettingsStickRange": "Intervalo:", + "DialogStopEmulationTitle": "Ryujinx - Parar emulação", + "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Áudio", + "SettingsTabNetwork": "Rede", + "SettingsTabNetworkConnection": "Conexão de rede", + "SettingsTabCpuCache": "Cache da CPU", + "SettingsTabCpuMemory": "Memória da CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", + "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", + "ControllerSettingsRotate90": "Rodar 90° sentido horário", + "IconSize": "Tamanho do ícone", + "IconSizeTooltip": "Muda o tamanho do ícone do jogo", + "MenuBarOptionsShowConsole": "Exibir console", + "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", + "UserErrorNoKeys": "Chaves não encontradas", + "UserErrorNoFirmware": "Firmware não encontrado", + "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", + "UserErrorApplicationNotFound": "Aplicativo não encontrado", + "UserErrorUnknown": "Erro desconhecido", + "UserErrorUndefined": "Erro indefinido", + "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", + "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", + "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", + "OpenSetupGuideMessage": "Abrir o guia de configuração", + "NoUpdate": "Sem atualizações", + "TitleUpdateVersionLabel": "Versão {0} - {1}", + "RyujinxInfo": "Ryujinx - Informação", + "RyujinxConfirm": "Ryujinx - Confirmação", + "FileDialogAllTypes": "Todos os tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", + "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado por Software", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", + "UpdaterRenaming": "Renomeando arquivos antigos...", + "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", + "UpdaterAddingFiles": "Adicionando novos arquivos...", + "UpdaterExtracting": "Extraíndo atualização...", + "UpdaterDownloading": "Baixando atualização...", + "Game": "Jogo", + "Docked": "TV", + "Handheld": "Portátil", + "ConnectionError": "Erro de conexão.", + "AboutPageDeveloperListMore": "{0} e mais...", + "ApiError": "Erro de API.", + "LoadingHeading": "Carregando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando Shaders", + "AllKeyboards": "Todos os teclados", + "OpenFileDialogTitle": "Selecione um arquivo suportado para abrir", + "OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído", + "AllSupportedFormats": "Todos os formatos suportados", + "RyujinxUpdater": "Atualizador do Ryujinx", + "SettingsTabHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de tela:", + "SettingsTabHotkeysShowUiHotkey": "Exibir UI:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Mudo:", + "ControllerMotionTitle": "Configurações do controle de movimento", + "ControllerRumbleTitle": "Configurações de vibração", + "SettingsSelectThemeFileDialogTitle": "Selecionar arquivo do tema", + "SettingsXamlThemeFile": "Arquivo de tema Xaml", + "AvatarWindowTitle": "Gerenciar contas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconhecido", + "Usage": "Uso", + "Writable": "Gravável", + "SelectDlcDialogTitle": "Selecionar arquivos de DLC", + "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "UserProfileWindowTitle": "Gerenciador de perfis de usuário", + "CheatWindowTitle": "Gerenciador de Cheats", + "DlcWindowTitle": "Gerenciador de DLC", + "UpdateWindowTitle": "Gerenciador de atualizações", + "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "UserProfilesEditProfile": "Editar selecionado", + "Cancel": "Cancelar", + "Save": "Salvar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Definir imagem de perfil", + "UserProfileEmptyNameError": "É necessário um nome", + "UserProfileNoImageError": "A imagem de perfil deve ser definida", + "GameUpdateWindowHeading": "{0} atualizações disponíveis para {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID de usuário:", + "SettingsTabGraphicsBackend": "Backend gráfico", + "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", + "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", + "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", + "SettingsTabGraphicsPreferredGpu": "GPU preferencial", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", + "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", + "SettingsGpuBackendRestartMessage": "Configurações do backend gráfico ou da GPU foram alteradas. Uma reinicialização é necessária para que as mudanças tenham efeito.", + "SettingsGpuBackendRestartSubMessage": "Deseja reiniciar agora?", + "RyujinxUpdaterMessage": "Você quer atualizar o Ryujinx para a última versão?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", + "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", + "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gerenciar jogos salvos", + "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", + "IrreversibleActionNote": "Esta ação não é reversível.", + "SaveManagerHeading": "Gerenciar jogos salvos para {0}", + "SaveManagerTitle": "Gerenciador de jogos salvos", + "Name": "Nome", + "Size": "Tamanho", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar contas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", + "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", + "GraphicsAATooltip": "Aplica anti-serrilhamento à renderização do jogo", + "GraphicsAALabel": "Anti-serrilhado:", + "GraphicsScalingFilterLabel": "Filtro de escala:", + "GraphicsScalingFilterTooltip": "Habilita escala do Framebuffer", + "GraphicsScalingFilterLevelLabel": "Nível", + "GraphicsScalingFilterLevelTooltip": "Define o nível do filtro de escala", + "SmaaLow": "SMAA Baixo", + "SmaaMedium": "SMAA Médio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuário", + "UserEditorTitleCreate": "Criar usuário", + "SettingsTabNetworkInterface": "Interface de rede:", + "NetworkInterfaceTooltip": "A interface de rede usada para recursos LAN (rede local)", + "NetworkInterfaceDefault": "Padrão", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json new file mode 100644 index 00000000..a2128e52 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -0,0 +1,656 @@ +{ + "Language": "Русский", + "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", + "SettingsTabInputDirectMouseAccess": "Прямой доступ к мыши", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", + "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", + "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", + "SettingsTabSystemUseHypervisor": "Использовать Гипервизор", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", + "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", + "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileExit": "_Выход", + "MenuBarOptions": "Опции", + "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", + "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStopEmulation": "Остановить эмуляцию", + "MenuBarOptionsSettings": "_Параметры", + "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarActions": "_Действия", + "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", + "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarTools": "_Инструменты", + "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", + "MenuBarToolsManageFileTypes": "Управление типами файлов", + "MenuBarToolsInstallFileTypes": "Установить типы файлов", + "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", + "MenuBarHelp": "Помощь", + "MenuBarHelpCheckForUpdates": "Проверка обновления", + "MenuBarHelpAbout": "О программе", + "MenuSearch": "Поиск...", + "GameListHeaderFavorite": "Избранные", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Название", + "GameListHeaderDeveloper": "Разработчик", + "GameListHeaderVersion": "Версия", + "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderLastPlayed": "Последняя игра", + "GameListHeaderFileExtension": "Расширение файла", + "GameListHeaderFileSize": "Размер файла", + "GameListHeaderPath": "Путь", + "GameListContextMenuOpenUserSaveDirectory": "Открыть папку сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", + "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", + "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями названий", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageDlc": "Управление DLC", + "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", + "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды приложений и игр", + "GameListContextMenuCacheManagement": "Управление кэшем", + "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", + "GameListContextMenuExtractData": "Извлечь данные", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Извлечение раздела ExeFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", + "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "StatusBarSystemVersion": "Версия системы: {0}", + "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", + "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", + "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", + "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", + "Settings": "Параметры", + "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneralGeneral": "Общее", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", + "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", + "SettingsTabGeneralHideCursor": "Скрыть курсор", + "SettingsTabGeneralHideCursorNever": "Никогда", + "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "SettingsTabGeneralHideCursorAlways": "Всегда", + "SettingsTabGeneralGameDirectories": "Папки с играми", + "SettingsTabGeneralAdd": "Добавить", + "SettingsTabGeneralRemove": "Удалить", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Основные настройки", + "SettingsTabSystemSystemRegion": "Регион Системы:", + "SettingsTabSystemSystemRegionJapan": "Япония", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Европа", + "SettingsTabSystemSystemRegionAustralia": "Австралия", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Язык системы:", + "SettingsTabSystemSystemLanguageJapanese": "Японский", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", + "SettingsTabSystemSystemLanguageFrench": "Французский", + "SettingsTabSystemSystemLanguageGerman": "Германский", + "SettingsTabSystemSystemLanguageItalian": "Итальянский", + "SettingsTabSystemSystemLanguageSpanish": "Испанский", + "SettingsTabSystemSystemLanguageChinese": "Китайский", + "SettingsTabSystemSystemLanguageKorean": "Корейский", + "SettingsTabSystemSystemLanguageDutch": "Нидерландский", + "SettingsTabSystemSystemLanguagePortuguese": "Португальский", + "SettingsTabSystemSystemLanguageRussian": "Русский", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", + "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", + "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", + "SettingsTabSystemSystemTime": "Время системы:", + "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", + "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", + "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", + "SettingsTabSystemAudioBackendDummy": "Муляж", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хаки", + "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", + "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", + "SettingsTabGraphics": "Графика", + "SettingsTabGraphicsAPI": "Графические API", + "SettingsTabGraphicsEnableShaderCache": "Включить кэш шейдеров", + "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Масштаб:", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", + "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", + "SettingsTabGraphicsShaderDumpPath": "Путь дампа графического шейдера:", + "SettingsTabLogging": "Журналирование", + "SettingsTabLoggingLogging": "Журналирование", + "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", + "SettingsTabLoggingEnableStubLogs": "Включить журнал-заглушку", + "SettingsTabLoggingEnableInfoLogs": "Включить информационный журнал", + "SettingsTabLoggingEnableWarningLogs": "Включить журнал предупреждений", + "SettingsTabLoggingEnableErrorLogs": "Включить журнал ошибок", + "SettingsTabLoggingEnableTraceLogs": "Включить журнал трассировки", + "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", + "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", + "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", + "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: Снижает производительность", + "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", + "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", + "SettingsTabInput": "Управление", + "SettingsTabInputEnableDockedMode": "Включить режим закрепления", + "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsButtonSave": "Сохранить", + "SettingsButtonClose": "Закрыть", + "SettingsButtonOk": "Ок", + "SettingsButtonCancel": "Отмена", + "SettingsButtonApply": "Применить", + "ControllerSettingsPlayer": "Игрок", + "ControllerSettingsPlayer1": "Игрок 1", + "ControllerSettingsPlayer2": "Игрок 2", + "ControllerSettingsPlayer3": "Игрок 3", + "ControllerSettingsPlayer4": "Игрок 4", + "ControllerSettingsPlayer5": "Игрок 5", + "ControllerSettingsPlayer6": "Игрок 6", + "ControllerSettingsPlayer7": "Игрок 7", + "ControllerSettingsPlayer8": "Игрок 8", + "ControllerSettingsHandheld": "Портативный", + "ControllerSettingsInputDevice": "Устройство ввода", + "ControllerSettingsRefresh": "Обновить", + "ControllerSettingsDeviceDisabled": "Отключить", + "ControllerSettingsControllerType": "Тип контроллера", + "ControllerSettingsControllerTypeHandheld": "Портативный", + "ControllerSettingsControllerTypeProController": "Pro Контроллер", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsProfile": "Профиль", + "ControllerSettingsProfileDefault": "По умолчанию", + "ControllerSettingsLoad": "Загрузить", + "ControllerSettingsAdd": "Добавить", + "ControllerSettingsRemove": "Удалить", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Направляющая панель", + "ControllerSettingsDPadUp": "Вверх", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Влево", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickUp": "Вверх", + "ControllerSettingsStickDown": "Вниз", + "ControllerSettingsStickLeft": "Влево", + "ControllerSettingsStickRight": "Вправо", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Инвертировать X ось", + "ControllerSettingsStickInvertYAxis": "Инвертировать Y ось", + "ControllerSettingsStickDeadzone": "Мёртвая зона:", + "ControllerSettingsLStick": "Левый стик", + "ControllerSettingsRStick": "Правый стик", + "ControllerSettingsTriggersLeft": "Триггеры слева", + "ControllerSettingsTriggersRight": "Триггеры справа", + "ControllerSettingsTriggersButtonsLeft": "Триггерные кнопки слева", + "ControllerSettingsTriggersButtonsRight": "Триггерные кнопки справа", + "ControllerSettingsTriggers": "Триггеры", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Левые кнопки", + "ControllerSettingsExtraButtonsRight": "Правые кнопки", + "ControllerSettingsMisc": "Разное", + "ControllerSettingsTriggerThreshold": "Порог срабатывания:", + "ControllerSettingsMotion": "Движение", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контроллера:", + "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", + "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", + "ControllerSettingsSave": "Сохранить", + "ControllerSettingsClose": "Закрыть", + "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", + "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", + "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", + "UserProfilesAddNewProfile": "Добавить новый профиль", + "UserProfilesDelete": "Удалить", + "UserProfilesClose": "Закрыть", + "ProfileNameSelectionWatermark": "Выберите псевдоним", + "ProfileImageSelectionTitle": "Выбор изображения профиля", + "ProfileImageSelectionHeader": "Выберите изображение профиля", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionImportImage": "Импорт файла изображения", + "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "InputDialogTitle": "Диалоговое окно ввода", + "InputDialogOk": "ОК", + "InputDialogCancel": "Отмена", + "InputDialogAddNewProfileTitle": "Выберите имя профиля", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", + "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", + "AvatarChoose": "Выбор аватара", + "AvatarSetBackgroundColor": "Установить цвет фона", + "AvatarClose": "Закрыть", + "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", + "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", + "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", + "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", + "MenuBarFileToolsHideUi": "Скрыть UI", + "GameListContextMenuRunApplication": "Запуск приложения", + "GameListContextMenuToggleFavorite": "Переключить Избранное", + "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", + "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", + "SettingsTabGeneralThemeBaseStyleLight": "Светлая", + "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", + "ButtonBrowse": "Обзор", + "ControllerSettingsConfigureGeneral": "Настройка", + "ControllerSettingsRumble": "Вибрация", + "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", + "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", + "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", + "DialogConfirmationTitle": "Ryujinx - Подтверждение", + "DialogUpdaterTitle": "Ryujinx - Обновление", + "DialogErrorTitle": "Ryujinx - Ошибка", + "DialogWarningTitle": "Ryujinx - Предупреждение", + "DialogExitTitle": "Ryujinx - Выход", + "DialogErrorMessage": "Ryujinx обнаружил ошибку", + "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", + "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", + "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", + "FolderDialogExtractTitle": "Выберите папку для извлечения", + "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", + "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", + "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", + "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", + "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", + "DialogUpdaterDownloadingMessage": "Загрузка обновления...", + "DialogUpdaterExtractionMessage": "Извлечение обновления...", + "DialogUpdaterRenamingMessage": "Переименование обновления...", + "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", + "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", + "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только x64 системы)", + "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", + "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", + "DialogRestartRequiredMessage": "Требуется перезагрузка", + "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", + "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", + "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", + "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", + "DialogInstallFileTypesSuccessMessage": "Успешно установлены типы файлов!", + "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", + "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", + "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", + "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogControllerAppletTitle": "Апплет контроллера", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", + "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", + "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", + "DialogProfileDeleteProfileTitle": "Удаление профиля", + "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", + "DialogWarning": "Внимание", + "DialogPPTCDeletionMessage": "Вы собираетесь удалить кэш PPTC для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", + "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", + "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", + "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", + "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", + "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", + "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", + "CommonAuto": "Автоматически", + "CommonOff": "Выключен", + "CommonOn": "Включен", + "InputDialogYes": "Да", + "InputDialogNo": "Нет", + "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продолжить", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", + "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", + "AboutRyujinxAboutTitle": "О программе:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Поддерживается:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", + "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AmiiboSeriesLabel": "Серия Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканировать", + "AmiiboOptionsShowAllLabel": "Показать все Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Велючено", + "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Полный путь", + "DlcManagerRemoveAllButton": "Убрать все", + "DlcManagerEnableAllButton": "Включить все", + "DlcManagerDisableAllButton": "Отключить все", + "MenuBarOptionsChangeLanguage": "Изменить язык", + "MenuBarShowFileTypes": "Показать типы файлов", + "CommonSort": "Сортировать", + "CommonShowNames": "Показать названия", + "CommonFavorite": "Избранные", + "OrderAscending": "По возрастанию", + "OrderDescending": "По убыванию", + "SettingsTabGraphicsFeatures": "Функции", + "ErrorWindowTitle": "Окно ошибки", + "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", + "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", + "AddGameDirTooltip": "AДобавить папку с игрой в список", + "RemoveGameDirTooltip": "Удалить выбранную папку игры", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", + "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", + "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", + "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", + "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", + "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", + "RegionTooltip": "Изменение региона системы", + "LanguageTooltip": "Изменение языка системы", + "TimezoneTooltip": "Изменение часового пояса системы", + "TimeTooltip": "Изменение системного времени", + "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш. Если планируете отключить вериткальную синхронизацию, мы рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", + "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для содействия в устранении неполадок.\n\nРекомендуется оставить включенным.", + "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", + "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", + "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстрее, но менее безопасно. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "UseHypervisorTooltip": "Использует Гипервизор вместо JIT. Значительно увеличивает производительность, но может быть работать нестабильно.", + "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch для разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Это поможет избежать вылеты при загрузке определенных игр.\n\nРекомендуется оставить выключенным.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на во втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", + "ResolutionScaleTooltip": "Масштабирование разрешения", + "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение в игре по умолчанию)", + "AspectRatioTooltip": "Соотношение сторон, применяемое к окну.", + "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", + "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", + "InfoLogTooltip": "Включает печать сообщений информационного журнала. Не влияет на производительность.", + "WarnLogTooltip": "Включает печать сообщений журнала предупреждений. Не влияет на производительность.", + "ErrorLogTooltip": "Включает печать сообщений журнала ошибок. Не влияет на производительность.", + "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", + "GuestLogTooltip": "Включает печать сообщений гостевого журнала. Не влияет на производительность.", + "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", + "DeveloperOptionTooltip": "Используйте с осторожностью", + "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", + "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", + "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", + "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", + "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "ExitTooltip": "Выйти из Ryujinx", + "OpenSettingsTooltip": "Открыть окно настроек", + "OpenProfileManagerTooltip": "Открыть диспетчер профилей", + "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", + "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", + "OpenAboutTooltip": "Открыть окно «О программе»", + "GridSize": "Размер сетки", + "GridSizeTooltip": "Изменение размера элементов сетки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", + "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", + "SettingsTabSystemAudioVolume": "Громкость: ", + "AudioVolumeTooltip": "Изменяет громкость звука", + "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет/сетевой режим", + "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", + "GameListContextMenuManageCheatToolTip": "Управление читами", + "GameListContextMenuManageCheat": "Управление читами", + "ControllerSettingsStickRange": "Диапазон:", + "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", + "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудио", + "SettingsTabNetwork": "Сеть", + "SettingsTabNetworkConnection": "Подключение к сети", + "SettingsTabCpuCache": "Кэш ЦП", + "SettingsTabCpuMemory": "Память ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Обновление выключено!", + "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку SD-карты Atmosphere, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", + "IconSize": "Размер иконки", + "IconSizeTooltip": "Изменить размер игровых иконок", + "MenuBarOptionsShowConsole": "Показать консоль", + "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", + "UserErrorNoKeys": "Ключи не найдены", + "UserErrorNoFirmware": "Прошивка не найдена", + "UserErrorFirmwareParsingFailed": "Ошибка извлечения прошивки", + "UserErrorApplicationNotFound": "Приложение не найдено", + "UserErrorUnknown": "Неизвестная ошибка", + "UserErrorUndefined": "Неопределенная ошибка", + "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", + "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти действительное приложение по указанному пути.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", + "OpenSetupGuideMessage": "Открыть руководство по установке", + "NoUpdate": "Нет обновлений", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Информация", + "RyujinxConfirm": "Ryujinx - Подтверждение", + "FileDialogAllTypes": "Все типы", + "Never": "Никогда", + "SwkbdMinCharacters": "Должно быть не менее {0} символов.", + "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", + "SoftwareKeyboard": "Программная клавиатура", + "SoftwareKeyboardModeNumbersOnly": "Должны быть только цифры", + "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", + "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", + "DialogControllerAppletMessagePlayerRange": "Приложение запрашивает {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте сейчас или нажмите \"Закрыть\".", + "DialogControllerAppletMessage": "Приложение запрашивает ровно {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте Ввод или нажмите \"Закрыть\".", + "DialogControllerAppletDockModeSet": "Установлен стационарный режим. Портативный режим недоступен.", + "UpdaterRenaming": "Переименование старых файлов...", + "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", + "UpdaterAddingFiles": "Добавление новых файлов...", + "UpdaterExtracting": "Извлечение обновления...", + "UpdaterDownloading": "Загрузка обновления...", + "Game": "Игра", + "Docked": "Стационарный режим", + "Handheld": "Портативный режим", + "ConnectionError": "Ошибка соединения!", + "AboutPageDeveloperListMore": "{0} и больше...", + "ApiError": "Ошибка API.", + "LoadingHeading": "Загрузка {0}", + "CompilingPPTC": "Компиляция PTC", + "CompilingShaders": "Компиляция шейдеров", + "AllKeyboards": "Все клавиатуры", + "OpenFileDialogTitle": "Выберите совместимый файл для открытия", + "OpenFolderDialogTitle": "Выберите папку с распакованной игрой", + "AllSupportedFormats": "Все поддерживаемые форматы", + "RyujinxUpdater": "Ryujinx - Обновление", + "SettingsTabHotkeys": "Горячие клавиши", + "SettingsTabHotkeysHotkeys": "Горячие клавиши", + "SettingsTabHotkeysToggleVsyncHotkey": "Переключить VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", + "SettingsTabHotkeysShowUiHotkey": "Показать UI:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Приглушить:", + "ControllerMotionTitle": "Настройки управления движением", + "ControllerRumbleTitle": "Настройки вибрации", + "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", + "SettingsXamlThemeFile": "Файл темы Xaml", + "AvatarWindowTitle": "Управление аккаунтами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Неизвестно", + "Usage": "Применение", + "Writable": "Доступно для записи", + "SelectDlcDialogTitle": "Выберите файлы DLC", + "SelectUpdateDialogTitle": "Выберите файлы обновления", + "UserProfileWindowTitle": "Менеджер профилей пользователей", + "CheatWindowTitle": "Менеджер читов", + "DlcWindowTitle": "Управление загружаемым контентом для {0} ({1})", + "UpdateWindowTitle": "Менеджер обновления названий", + "CheatWindowHeading": "Читы доступны для {0} [{1}]", + "BuildId": "ID версии:", + "DlcWindowHeading": "{0} Загружаемый контент", + "UserProfilesEditProfile": "Изменить выбранные", + "Cancel": "Отмена", + "Save": "Сохранить", + "Discard": "Отменить", + "UserProfilesSetProfileImage": "Установить изображение профиля", + "UserProfileEmptyNameError": "Имя обязательно", + "UserProfileNoImageError": "Изображение профиля должно быть установлено", + "GameUpdateWindowHeading": "Обновление доступно для {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", + "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", + "UserProfilesName": "Имя:", + "UserProfilesUserId": "ID пользователя:", + "SettingsTabGraphicsBackend": "Бэкенд графики", + "SettingsTabGraphicsBackendTooltip": "Использовать графический бэкенд", + "SettingsEnableTextureRecompression": "Включить пережатие текстур", + "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для GPU с 4 гб видеопамяти и менее.\n\nРекомендуется оставить выключенным.", + "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите видеокарту, которая будет использоваться с графическим бэкендом Vulkan.\n\nНе влияет на GPU, который будет использовать OpenGL.\n\nУстановите графический процессор, помеченный как \"dGPU\", если вы не уверены. Если его нет, оставьте нетронутым.", + "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", + "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", + "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", + "RyujinxUpdaterMessage": "Вы хотите обновить Ryujinx до последней версии?", + "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", + "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", + "SettingsEnableMacroHLE": "Включить Macro HLE", + "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", + "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", + "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкэнд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", + "VolumeShort": "Громкость", + "UserProfilesManageSaves": "Управление сохранениями", + "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", + "IrreversibleActionNote": "Данное действие является необратимым.", + "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", + "SaveManagerTitle": "Менеджер сохранений", + "Name": "Название", + "Size": "Размер", + "Search": "Поиск", + "UserProfilesRecoverLostAccounts": "Восстановить утерянные аккаунты", + "Recover": "Восстановление", + "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", + "UserProfilesRecoverEmptyList": "Нет профилей для восстановления", + "GraphicsAATooltip": "Применяет сглаживание к рейдеру игры.", + "GraphicsAALabel": "Сглаживание:", + "GraphicsScalingFilterLabel": "Масштабирующий фильтр:", + "GraphicsScalingFilterTooltip": "Включает масштабирование кадрового буфера", + "GraphicsScalingFilterLevelLabel": "Уровень", + "GraphicsScalingFilterLevelTooltip": "Установить уровень фильтра масштабирования", + "SmaaLow": "SMAA Низкое", + "SmaaMedium": "SMAA Среднее", + "SmaaHigh": "SMAA Высокое", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редактирование пользователя", + "UserEditorTitleCreate": "Создание пользователя", + "SettingsTabNetworkInterface": "Сетевой Интерфейс", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN", + "NetworkInterfaceDefault": "По умолчанию", + "PackagingShaders": "Упаковка шейдеров", + "AboutChangelogButton": "Список изменений на GitHub", + "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии в браузере." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json new file mode 100644 index 00000000..3f70a781 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -0,0 +1,656 @@ +{ + "Language": "Türkçe", + "MenuBarFileOpenApplet": "Applet'i Aç", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", + "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", + "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", + "SettingsTabSystemMemoryManagerModeSoftware": "Yazılım", + "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", + "SettingsTabSystemUseHypervisor": "Hypervisor Kullan", + "MenuBarFile": "_Dosya", + "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", + "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", + "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", + "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", + "MenuBarFileExit": "_Çıkış", + "MenuBarOptions": "Seçenekler", + "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", + "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", + "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", + "MenuBarOptionsSettings": "_Seçenekler", + "MenuBarOptionsManageUserProfiles": "_Kullanıcı Profillerini Yönet", + "MenuBarActions": "_Eylemler", + "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", + "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", + "MenuBarTools": "_Araçlar", + "MenuBarToolsInstallFirmware": "Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Bir Dizin Üzerinden Yazılım Yükle", + "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", + "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", + "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", + "MenuBarHelp": "Yardım", + "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", + "MenuBarHelpAbout": "Hakkında", + "MenuSearch": "Ara...", + "GameListHeaderFavorite": "Favori", + "GameListHeaderIcon": "Simge", + "GameListHeaderApplication": "Oyun Adı", + "GameListHeaderDeveloper": "Geliştirici", + "GameListHeaderVersion": "Sürüm", + "GameListHeaderTimePlayed": "Oynama Süresi", + "GameListHeaderLastPlayed": "Son Oynama Tarihi", + "GameListHeaderFileExtension": "Dosya Uzantısı", + "GameListHeaderFileSize": "Dosya Boyutu", + "GameListHeaderPath": "Yol", + "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", + "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", + "GameListContextMenuManageDlc": "DLC'leri Yönet", + "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", + "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", + "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", + "GameListContextMenuCacheManagement": "Önbellek Yönetimi", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Önbelleğini Temizle", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader önbelleğini temizler", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Önbelleğinin bulunduğu dizini açar", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Önbelleği Dizinini Aç", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader önbelleğinin bulunduğu dizini açar", + "GameListContextMenuExtractData": "Veriyi Ayıkla", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataLogo": "Simge", + "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", + "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count değerini {0} sayısına yükseltmek ister misiniz", + "LinuxVmMaxMapCountDialogTextSecondary": "Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Evet, bir sonraki yeniden başlatmaya kadar", + "LinuxVmMaxMapCountDialogButtonPersistent": "Evet, kalıcı olarak", + "LinuxVmMaxMapCountWarningTextPrimary": "İzin verilen maksimum bellek haritası değeri tavsiye edildiğinden daha düşük. ", + "LinuxVmMaxMapCountWarningTextSecondary": "Şu anki vm.max_map_count değeri {0}, bu {1} değerinden daha az. Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.\n\nManuel olarak bu limiti arttırmayı deneyebilir ya da pkexec'i yükleyebilirsiniz, bu da Ryujinx'in yardımcı olmasına izin verir.", + "Settings": "Ayarlar", + "SettingsTabGeneral": "Kullancı Arayüzü", + "SettingsTabGeneralGeneral": "Genel", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", + "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:", + "SettingsTabGeneralHideCursorNever": "Hiçbir Zaman", + "SettingsTabGeneralHideCursorOnIdle": "Hareketsiz Durumda", + "SettingsTabGeneralHideCursorAlways": "Her Zaman", + "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", + "SettingsTabGeneralAdd": "Ekle", + "SettingsTabGeneralRemove": "Kaldır", + "SettingsTabSystem": "Sistem", + "SettingsTabSystemCore": "Çekirdek", + "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", + "SettingsTabSystemSystemRegionJapan": "Japonya", + "SettingsTabSystemSystemRegionUSA": "ABD", + "SettingsTabSystemSystemRegionEurope": "Avrupa", + "SettingsTabSystemSystemRegionAustralia": "Avustralya", + "SettingsTabSystemSystemRegionChina": "Çin", + "SettingsTabSystemSystemRegionKorea": "Kore", + "SettingsTabSystemSystemRegionTaiwan": "Tayvan", + "SettingsTabSystemSystemLanguage": "Sistem Dili:", + "SettingsTabSystemSystemLanguageJapanese": "Japonca", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", + "SettingsTabSystemSystemLanguageFrench": "Fransızca", + "SettingsTabSystemSystemLanguageGerman": "Almanca", + "SettingsTabSystemSystemLanguageItalian": "İtalyanca", + "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", + "SettingsTabSystemSystemLanguageChinese": "Çince", + "SettingsTabSystemSystemLanguageKorean": "Korece", + "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", + "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", + "SettingsTabSystemSystemLanguageRussian": "Rusça", + "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", + "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", + "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", + "SettingsTabSystemSystemTime": "Sistem Saati:", + "SettingsTabSystemEnableVsync": "Dikey Eşitleme", + "SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri", + "SettingsTabSystemAudioBackend": "Ses Motoru:", + "SettingsTabSystemAudioBackendDummy": "Yapay", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hack'ler", + "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", + "SettingsTabSystemExpandDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", + "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", + "SettingsTabGraphics": "Grafikler", + "SettingsTabGraphicsAPI": "Grafikler API", + "SettingsTabGraphicsEnableShaderCache": "Shader Önbelleğini Etkinleştir", + "SettingsTabGraphicsAnisotropicFiltering": "Eşyönsüz Doku Süzmesi:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Çözünürlük Ölçeği:", + "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", + "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak İçin Genişlet", + "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", + "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", + "SettingsTabLogging": "Loglama", + "SettingsTabLoggingLogging": "Loglama", + "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", + "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", + "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", + "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", + "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", + "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", + "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", + "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", + "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", + "SettingsTabLoggingDeveloperOptionsNote": "UYARI: Oyun performansı azalacak", + "SettingsTabLoggingGraphicsBackendLogLevel": "Grafik Arka Uç Günlük Düzeyi", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiçbiri", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Hata", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Hepsi", + "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", + "SettingsTabInput": "Giriş Yöntemi", + "SettingsTabInputEnableDockedMode": "Docked Modu Etkinleştir", + "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", + "SettingsButtonSave": "Kaydet", + "SettingsButtonClose": "Kapat", + "SettingsButtonOk": "Tamam", + "SettingsButtonCancel": "İptal", + "SettingsButtonApply": "Uygula", + "ControllerSettingsPlayer": "Oyuncu", + "ControllerSettingsPlayer1": "Oyuncu 1", + "ControllerSettingsPlayer2": "Oyuncu 2", + "ControllerSettingsPlayer3": "Oyuncu 3", + "ControllerSettingsPlayer4": "Oyuncu 4", + "ControllerSettingsPlayer5": "Oyuncu 5", + "ControllerSettingsPlayer6": "Oyuncu 6", + "ControllerSettingsPlayer7": "Oyuncu 7", + "ControllerSettingsPlayer8": "Oyuncu 8", + "ControllerSettingsHandheld": "Portatif Mod", + "ControllerSettingsInputDevice": "Giriş Cihazı", + "ControllerSettingsRefresh": "Yenile", + "ControllerSettingsDeviceDisabled": "Devre Dışı", + "ControllerSettingsControllerType": "Kontrolcü Tipi", + "ControllerSettingsControllerTypeHandheld": "Portatif Mod", + "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Varsayılan", + "ControllerSettingsLoad": "Yükle", + "ControllerSettingsAdd": "Ekle", + "ControllerSettingsRemove": "Kaldır", + "ControllerSettingsButtons": "Tuşlar", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Yön Tuşları", + "ControllerSettingsDPadUp": "Yukarı", + "ControllerSettingsDPadDown": "Aşağı", + "ControllerSettingsDPadLeft": "Sol", + "ControllerSettingsDPadRight": "Sağ", + "ControllerSettingsStickButton": "Tuş", + "ControllerSettingsStickUp": "Yukarı", + "ControllerSettingsStickDown": "Aşağı", + "ControllerSettingsStickLeft": "Sol", + "ControllerSettingsStickRight": "Sağ", + "ControllerSettingsStickStick": "Analog", + "ControllerSettingsStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsStickDeadzone": "Ölü Bölge", + "ControllerSettingsLStick": "Sol Analog", + "ControllerSettingsRStick": "Sağ Analog", + "ControllerSettingsTriggersLeft": "Tetikler Sol", + "ControllerSettingsTriggersRight": "Tetikler Sağ", + "ControllerSettingsTriggersButtonsLeft": "Tetik Tuşları Sol", + "ControllerSettingsTriggersButtonsRight": "Tetik Tuşları Sağ", + "ControllerSettingsTriggers": "Tetikler", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tuşlar Sol", + "ControllerSettingsExtraButtonsRight": "Tuşlar Sağ", + "ControllerSettingsMisc": "Diğer", + "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", + "ControllerSettingsMotion": "Hareket", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", + "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionMirrorInput": "Girişi Aynala", + "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", + "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", + "ControllerSettingsSave": "Kaydet", + "ControllerSettingsClose": "Kapat", + "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", + "UserProfilesSaveProfileName": "Profil İsmini Kaydet", + "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", + "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", + "UserProfilesAddNewProfile": "Yeni Profil Ekle", + "UserProfilesDelete": "Sil", + "UserProfilesClose": "Kapat", + "ProfileNameSelectionWatermark": "Kullanıcı Adı Seç", + "ProfileImageSelectionTitle": "Profil Resmi Seçimi", + "ProfileImageSelectionHeader": "Profil Resmi Seç", + "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", + "ProfileImageSelectionImportImage": "Resim İçeri Aktar", + "ProfileImageSelectionSelectAvatar": "Yazılım Avatarı Seç", + "InputDialogTitle": "Giriş Yöntemi Diyaloğu", + "InputDialogOk": "Tamam", + "InputDialogCancel": "İptal", + "InputDialogAddNewProfileTitle": "Profil İsmini Seç", + "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", + "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", + "AvatarChoose": "Seç", + "AvatarSetBackgroundColor": "Arka Plan Rengi Ayarla", + "AvatarClose": "Kapat", + "ControllerSettingsLoadProfileToolTip": "Profil Yükle", + "ControllerSettingsAddProfileToolTip": "Profil Ekle", + "ControllerSettingsRemoveProfileToolTip": "Profili Kaldır", + "ControllerSettingsSaveProfileToolTip": "Profili Kaydet", + "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", + "MenuBarFileToolsHideUi": "Arayüzü Gizle", + "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", + "GameListContextMenuToggleFavorite": "Favori Ayarla", + "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", + "SettingsTabGeneralThemeBaseStyle": "Temel Stil", + "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", + "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", + "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", + "ButtonBrowse": "Göz At", + "ControllerSettingsConfigureGeneral": "Ayarla", + "ControllerSettingsRumble": "Titreşim", + "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", + "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Seviyesi", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi bulunamadı", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", + "DialogConfirmationTitle": "Ryujinx - Onay", + "DialogUpdaterTitle": "Ryujinx - Güncelleyici", + "DialogErrorTitle": "Ryujinx - Hata", + "DialogWarningTitle": "Ryujinx - Uyarı", + "DialogExitTitle": "Ryujinx - Çıkış", + "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", + "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", + "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken bir hata oluştu: {0}", + "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", + "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", + "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", + "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için kayıt dosyasını okuyun.", + "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", + "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub tarafından sürüm bilgileri alınırken bir hata oluştu. Eğer yeni sürüm için hazırlıklar yapılıyorsa bu hatayı almanız olasıdır. Lütfen birkaç dakika sonra tekrar deneyiniz.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterDownloadingMessage": "Güncelleme İndiriliyor...", + "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", + "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", + "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", + "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", + "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", + "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", + "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", + "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", + "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", + "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm için lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", + "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", + "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", + "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", + "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", + "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", + "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", + "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", + "DialogInstallFileTypesErrorMessage": "Dosya uzantıları yükleme işlemi başarısız oldu.", + "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", + "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", + "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogControllerAppletTitle": "Kontrolcü Applet'i", + "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", + "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", + "DialogProfileDeleteProfileTitle": "Profil Siliniyor", + "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", + "DialogWarning": "Uyarı", + "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", + "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogInvalidTitleIdErrorMessage": "Arayüz hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", + "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", + "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", + "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", + "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", + "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", + "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", + "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", + "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", + "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", + "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", + "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "SettingsTabGraphicsFeaturesOptions": "Özellikler", + "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", + "CommonAuto": "Otomatik", + "CommonOff": "Kapalı", + "CommonOn": "Açık", + "InputDialogYes": "Evet", + "InputDialogNo": "Hayır", + "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçersiz karakter içeriyor. Lütfen tekrar deneyin.", + "MenuBarOptionsPauseEmulation": "Durdur", + "MenuBarOptionsResumeEmulation": "Devam Et", + "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", + "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", + "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", + "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxAboutTitle": "Hakkında:", + "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", + "AmiiboSeriesLabel": "Amiibo Serisi", + "AmiiboCharacterLabel": "Karakter", + "AmiiboScanButtonLabel": "Tarat", + "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", + "AmiiboOptionsUsRandomTagLabel": "Hack: Rastgele bir Uuid kullan", + "DlcManagerTableHeadingEnabledLabel": "Etkin", + "DlcManagerTableHeadingTitleIdLabel": "Başlık ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Yol", + "DlcManagerTableHeadingFullPathLabel": "Tam Yol", + "DlcManagerRemoveAllButton": "Tümünü kaldır", + "DlcManagerEnableAllButton": "Tümünü Aktif Et", + "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "MenuBarOptionsChangeLanguage": "Dili Değiştir", + "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", + "CommonSort": "Sırala", + "CommonShowNames": "İsimleri Göster", + "CommonFavorite": "Favori", + "OrderAscending": "Artan", + "OrderDescending": "Azalan", + "SettingsTabGraphicsFeatures": "Özellikler & İyileştirmeler", + "ErrorWindowTitle": "Hata Penceresi", + "ToggleDiscordTooltip": "Ryujinx'i \"şimdi oynanıyor\" Discord aktivitesinde göstermeyi veya göstermemeyi seçin", + "AddGameDirBoxTooltip": "Listeye eklemek için oyun dizini seçin", + "AddGameDirTooltip": "Listeye oyun dizini ekle", + "RemoveGameDirTooltip": "Seçili oyun dizinini kaldır", + "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", + "CustomThemePathTooltip": "Özel arayüz temasının yolu", + "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", + "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", + "RegionTooltip": "Sistem Bölgesini Değiştir", + "LanguageTooltip": "Sistem Dilini Değiştir", + "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", + "TimeTooltip": "Sistem Saatini Değiştir", + "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", + "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", + "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", + "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", + "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", + "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", + "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", + "MemoryManagerUnsafeTooltip": "Hafızayı doğrudan tahsis eder, ancak host aralığına erişimden önce adresi maskelemez. Daha iyi performansa karşılık emniyetten ödün verir. Misafir uygulama Ryujinx içerisinden istediği hafızaya erişebilir, bu sebeple bu seçenek ile sadece güvendiğiniz uygulamaları çalıştırın.", + "UseHypervisorTooltip": "JIT yerine Hypervisor kullan. Uygun durumlarda performansı büyük oranda arttırır. Ancak şu anki halinde stabil durumda çalışmayabilir.", + "DRamTooltip": "Emüle edilen sistem hafızasını 4GiB'dan 6GiB'a yükseltir.\n\nBu seçenek yalnızca yüksek çözünürlük doku paketleri veya 4k çözünürlük modları için kullanılır. Performansı artırMAZ!\n\nEmin değilseniz devre dışı bırakın.", + "IgnoreMissingServicesTooltip": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.", + "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", + "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", + "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", + "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", + "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", + "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", + "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", + "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", + "InfoLogTooltip": "Bilgi log mesajlarını konsola yazdırır. Performansı etkilemez.", + "WarnLogTooltip": "Uyarı log mesajlarını konsola yazdırır. Performansı etkilemez.", + "ErrorLogTooltip": "Hata log mesajlarını konsola yazdırır. Performansı etkilemez.", + "TraceLogTooltip": "Trace log mesajlarını konsola yazdırır. Performansı etkilemez.", + "GuestLogTooltip": "Guest log mesajlarını konsola yazdırır. Performansı etkilemez.", + "FileAccessLogTooltip": "Dosya sistemi erişim log mesajlarını konsola yazdırır.", + "FSAccessLogModeTooltip": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür", + "DeveloperOptionTooltip": "Dikkatli kullanın", + "OpenGlLogLevel": "Uygun log seviyesinin aktif olmasını gerektirir", + "DebugLogTooltip": "Debug log mesajlarını konsola yazdırır.\n\nBu seçeneği yalnızca geliştirici üyemiz belirtirse aktifleştirin, çünkü bu seçenek log dosyasını okumayı zorlaştırır ve emülatörün performansını düşürür.", + "LoadApplicationFileTooltip": "Switch ile uyumlu bir dosya yüklemek için dosya tarayıcısını açar", + "LoadApplicationFolderTooltip": "Switch ile uyumlu ayrıştırılmamış bir uygulama yüklemek için dosya tarayıcısını açar", + "OpenRyujinxFolderTooltip": "Ryujinx dosya sistem klasörünü açar", + "OpenRyujinxLogsTooltip": "Log dosyalarının bulunduğu klasörü açar", + "ExitTooltip": "Ryujinx'ten çıkış yapmayı sağlar", + "OpenSettingsTooltip": "Seçenekler penceresini açar", + "OpenProfileManagerTooltip": "Kullanıcı profil yöneticisi penceresini açar", + "StopEmulationTooltip": "Oynanmakta olan oyunun emülasyonunu durdurup oyun seçimine geri döndürür", + "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetlemeyi sağlar", + "OpenAboutTooltip": "Hakkında penceresini açar", + "GridSize": "Öge Boyutu", + "GridSizeTooltip": "Grid ögelerinin boyutunu değiştirmeyi sağlar", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", + "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", + "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", + "AudioVolumeTooltip": "Ses seviyesini değiştirir", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimi/LAN Modu", + "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", + "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", + "GameListContextMenuManageCheat": "Hileleri Yönet", + "ControllerSettingsStickRange": "Menzil:", + "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", + "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", + "SettingsTabCpu": "İşlemci", + "SettingsTabAudio": "Ses", + "SettingsTabNetwork": "Ağ", + "SettingsTabNetworkConnection": "Ağ Bağlantısı", + "SettingsTabCpuCache": "İşlemci Belleği", + "SettingsTabCpuMemory": "CPU Hafızası", + "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", + "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", + "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", + "IconSize": "Ikon Boyutu", + "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", + "MenuBarOptionsShowConsole": "Konsol'u Göster", + "ShaderCachePurgeError": "Belirtilen shader cache temizlenirken hata {0}: {1}", + "UserErrorNoKeys": "Keys bulunamadı", + "UserErrorNoFirmware": "Firmware bulunamadı", + "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", + "UserErrorApplicationNotFound": "Uygulama bulunamadı", + "UserErrorUnknown": "Bilinmeyen hata", + "UserErrorUndefined": "Tanımlanmayan hata", + "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyasını bulamadı", + "UserErrorNoFirmwareDescription": "Ryujinx yüklü herhangi firmware bulamadı", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.", + "UserErrorApplicationNotFoundDescription": "Ryujinx belirtilen yolda geçerli bir uygulama bulamadı.", + "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", + "UserErrorUndefinedDescription": "Tanımlanmayan bir hata oluştu! Bu durum ile karşılaşılmamalıydı, lütfen bir geliştirici ile iletişime geçin!", + "OpenSetupGuideMessage": "Kurulum Kılavuzunu Aç", + "NoUpdate": "Güncelleme Yok", + "TitleUpdateVersionLabel": "Sürüm {0} - {1}", + "RyujinxInfo": "Ryujinx - Bilgi", + "RyujinxConfirm": "Ryujinx - Doğrulama", + "FileDialogAllTypes": "Tüm türler", + "Never": "Hiçbir Zaman", + "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", + "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", + "SoftwareKeyboard": "Yazılım Klavyesi", + "SoftwareKeyboardModeNumbersOnly": "Sadece Numara Olabilir", + "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", + "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", + "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", + "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", + "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", + "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", + "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", + "UpdaterDownloading": "Güncelleme İndiriliyor...", + "Game": "Oyun", + "Docked": "Yerleştirildi", + "Handheld": "El tipi", + "ConnectionError": "Bağlantı Hatası.", + "AboutPageDeveloperListMore": "{0} ve daha fazla...", + "ApiError": "API Hatası.", + "LoadingHeading": "{0} Yükleniyor", + "CompilingPPTC": "PTC Derleniyor", + "CompilingShaders": "Shaderlar Derleniyor", + "AllKeyboards": "Tüm Klavyeler", + "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", + "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", + "AllSupportedFormats": "Tüm Desteklenen Formatlar", + "RyujinxUpdater": "Ryujinx Güncelleyicisi", + "SettingsTabHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir/Devre Dışı Bırak:", + "SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:", + "SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:", + "SettingsTabHotkeysPauseHotkey": "Durdur:", + "SettingsTabHotkeysToggleMuteHotkey": "Sustur:", + "ControllerMotionTitle": "Hareket Kontrol Seçenekleri", + "ControllerRumbleTitle": "Titreşim Seçenekleri", + "SettingsSelectThemeFileDialogTitle": "Tema Dosyası Seç", + "SettingsXamlThemeFile": "Xaml Tema Dosyası", + "AvatarWindowTitle": "Hesapları Yönet - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Bilinmeyen", + "Usage": "Kullanım", + "Writable": "Yazılabilir", + "SelectDlcDialogTitle": "DLC dosyalarını seç", + "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", + "CheatWindowTitle": "Oyun Hilelerini Yönet", + "DlcWindowTitle": "Oyun DLC'lerini Yönet", + "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", + "CheatWindowHeading": "{0} için Hile mevcut [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "UserProfilesEditProfile": "Seçiliyi Düzenle", + "Cancel": "İptal", + "Save": "Kaydet", + "Discard": "Iskarta", + "UserProfilesSetProfileImage": "Profil Resmi Ayarla", + "UserProfileEmptyNameError": "İsim gerekli", + "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", + "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", + "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:", + "UserProfilesName": "İsim:", + "UserProfilesUserId": "Kullanıcı Adı:", + "SettingsTabGraphicsBackend": "Grafik Arka Ucu", + "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", + "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", + "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", + "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", + "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", + "SettingsGpuBackendRestartMessage": "Grafik Motoru ya da GPU ayarları değiştirildi. Bu işlemin uygulanması için yeniden başlatma gerekli.", + "SettingsGpuBackendRestartSubMessage": "Şimdi yeniden başlatmak istiyor musunuz?", + "RyujinxUpdaterMessage": "Ryujinx'i en son sürüme güncellemek ister misiniz?", + "SettingsTabHotkeysVolumeUpHotkey": "Sesi Arttır:", + "SettingsTabHotkeysVolumeDownHotkey": "Sesi Azalt:", + "SettingsEnableMacroHLE": "Macro HLE'yi Aktifleştir", + "SettingsEnableMacroHLETooltip": "GPU Macro kodunun yüksek seviye emülasyonu.\n\nPerformansı arttırır, ama bazı oyunlarda grafik hatalarına yol açabilir.\n\nEmin değilseniz AÇIK bırakın.", + "SettingsEnableColorSpacePassthrough": "Renk Alanı Geçişi", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan Backend'ini renk alanı belirtmeden renk bilgisinden geçmeye yönlendirir. Geniş gam ekranlı kullanıcılar için bu, renk doğruluğu pahasına daha canlı renklerle sonuçlanabilir.", + "VolumeShort": "Ses", + "UserProfilesManageSaves": "Kayıtları Yönet", + "DeleteUserSave": "Bu oyun için kullanıcı kaydını silmek istiyor musunuz?", + "IrreversibleActionNote": "Bu eylem geri alınamaz.", + "SaveManagerHeading": "{0} için Kayıt Dosyalarını Yönet", + "SaveManagerTitle": "Kayıt Yöneticisi", + "Name": "İsim", + "Size": "Boyut", + "Search": "Ara", + "UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar", + "Recover": "Kurtar", + "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", + "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", + "GraphicsAATooltip": "Oyuna Kenar Yumuşatma Ekler", + "GraphicsAALabel": "Kenar Yumuşatma:", + "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", + "GraphicsScalingFilterTooltip": "Çerçeve Arabellek Filtresini Açar", + "GraphicsScalingFilterLevelLabel": "Seviye", + "GraphicsScalingFilterLevelTooltip": "Ölçekleme Filtre Seviyesini Belirle", + "SmaaLow": "Düşük SMAA", + "SmaaMedium": "Orta SMAA", + "SmaaHigh": "Yüksek SMAA", + "SmaaUltra": "En Yüksek SMAA", + "UserEditorTitle": "Kullanıcıyı Düzenle", + "UserEditorTitleCreate": "Kullanıcı Oluştur", + "SettingsTabNetworkInterface": "Ağ Bağlantısı:", + "NetworkInterfaceTooltip": "LAN özellikleri için kullanılan ağ bağlantısı", + "NetworkInterfaceDefault": "Varsayılan", + "PackagingShaders": "Gölgeler Paketleniyor", + "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", + "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın" +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json new file mode 100644 index 00000000..a119fb4b --- /dev/null +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -0,0 +1,656 @@ +{ + "Language": "Yкраїнська", + "MenuBarFileOpenApplet": "Відкрити аплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", + "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", + "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", + "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", + "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", + "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", + "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", + "MenuBarFileExit": "_Вихід", + "MenuBarOptions": "Опції", + "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", + "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", + "MenuBarOptionsStopEmulation": "Зупинити емуляцію", + "MenuBarOptionsSettings": "_Налаштування", + "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", + "MenuBarActions": "_Дії", + "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", + "MenuBarActionsScanAmiibo": "Сканувати Amiibo", + "MenuBarTools": "_Інструменти", + "MenuBarToolsInstallFirmware": "Встановити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarHelp": "Довідка", + "MenuBarHelpCheckForUpdates": "Перевірити оновлення", + "MenuBarHelpAbout": "Про програму", + "MenuSearch": "Пошук...", + "GameListHeaderFavorite": "Вибране", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Назва", + "GameListHeaderDeveloper": "Розробник", + "GameListHeaderVersion": "Версія", + "GameListHeaderTimePlayed": "Зіграно часу", + "GameListHeaderLastPlayed": "Остання гра", + "GameListHeaderFileExtension": "Розширення файлу", + "GameListHeaderFileSize": "Розмір файлу", + "GameListHeaderPath": "Шлях", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", + "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", + "GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми", + "GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків", + "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", + "GameListContextMenuManageDlc": "Керування DLC", + "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", + "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", + "GameListContextMenuCacheManagement": "Керування кешем", + "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистити кеш шейдерів", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Видаляє кеш шейдерів програми", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Відкрити каталог PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Відкриває каталог, який містить кеш PPTC програми", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Відкрити каталог кешу шейдерів", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Відкриває каталог, який містить кеш шейдерів програми", + "GameListContextMenuExtractData": "Видобути дані", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Видобуває розділ ExeFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", + "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", + "StatusBarSystemVersion": "Версія системи: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Налаштування", + "SettingsTabGeneral": "Інтерфейс користувача", + "SettingsTabGeneralGeneral": "Загальні", + "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", + "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", + "SettingsTabGeneralHideCursor": "Hide Cursor:", + "SettingsTabGeneralHideCursorNever": "Never", + "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", + "SettingsTabGeneralHideCursorAlways": "Always", + "SettingsTabGeneralGameDirectories": "Каталоги ігор", + "SettingsTabGeneralAdd": "Додати", + "SettingsTabGeneralRemove": "Видалити", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Ядро", + "SettingsTabSystemSystemRegion": "Регіон системи:", + "SettingsTabSystemSystemRegionJapan": "Японія", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Європа", + "SettingsTabSystemSystemRegionAustralia": "Австралія", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Мова системи:", + "SettingsTabSystemSystemLanguageJapanese": "Японська", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Англійська (США)", + "SettingsTabSystemSystemLanguageFrench": "Французька", + "SettingsTabSystemSystemLanguageGerman": "Німецька", + "SettingsTabSystemSystemLanguageItalian": "Італійська", + "SettingsTabSystemSystemLanguageSpanish": "Іспанська", + "SettingsTabSystemSystemLanguageChinese": "Китайська", + "SettingsTabSystemSystemLanguageKorean": "Корейська", + "SettingsTabSystemSystemLanguageDutch": "Нідерландська", + "SettingsTabSystemSystemLanguagePortuguese": "Португальська", + "SettingsTabSystemSystemLanguageRussian": "Російська", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванська", + "SettingsTabSystemSystemLanguageBritishEnglish": "Англійська (Великобританія)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французька (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Іспанська (Латиноамериканська)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Спрощена китайська", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська", + "SettingsTabSystemSystemTimeZone": "Часовий пояс системи:", + "SettingsTabSystemSystemTime": "Час системи:", + "SettingsTabSystemEnableVsync": "Вертикальна синхронізація", + "SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)", + "SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS", + "SettingsTabSystemAudioBackend": "Аудіосистема:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хитрощі", + "SettingsTabSystemHacksNote": " (може викликати нестабільність)", + "SettingsTabSystemExpandDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", + "SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби", + "SettingsTabGraphics": "Графіка", + "SettingsTabGraphicsAPI": "Графічний API", + "SettingsTabGraphicsEnableShaderCache": "Увімкнути кеш шейдерів", + "SettingsTabGraphicsAnisotropicFiltering": "Анізотропна фільтрація:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Авто", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Роздільна здатність:", + "SettingsTabGraphicsResolutionScaleCustom": "Користувацька (не рекомендовано)", + "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", + "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", + "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", + "SettingsTabLogging": "Налагодження", + "SettingsTabLoggingLogging": "Налагодження", + "SettingsTabLoggingEnableLoggingToFile": "Увімкнути налагодження у файл", + "SettingsTabLoggingEnableStubLogs": "Увімкнути журнали заглушки", + "SettingsTabLoggingEnableInfoLogs": "Увімкнути інформаційні журнали", + "SettingsTabLoggingEnableWarningLogs": "Увімкнути журнали попереджень", + "SettingsTabLoggingEnableErrorLogs": "Увімкнути журнали помилок", + "SettingsTabLoggingEnableTraceLogs": "Увімкнути журнали трасування", + "SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей", + "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", + "SettingsTabLoggingEnableDebugLogs": "Увімкнути журнали налагодження", + "SettingsTabInput": "Введення", + "SettingsTabInputEnableDockedMode": "Режим док-станції", + "SettingsTabInputDirectKeyboardAccess": "Прямий доступ з клавіатури", + "SettingsButtonSave": "Зберегти", + "SettingsButtonClose": "Закрити", + "SettingsButtonOk": "Гаразд", + "SettingsButtonCancel": "Скасувати", + "SettingsButtonApply": "Застосувати", + "ControllerSettingsPlayer": "Гравець", + "ControllerSettingsPlayer1": "Гравець 1", + "ControllerSettingsPlayer2": "Гравець 2", + "ControllerSettingsPlayer3": "Гравець 3", + "ControllerSettingsPlayer4": "Гравець 4", + "ControllerSettingsPlayer5": "Гравець 5", + "ControllerSettingsPlayer6": "Гравець 6", + "ControllerSettingsPlayer7": "Гравець 7", + "ControllerSettingsPlayer8": "Гравець 8", + "ControllerSettingsHandheld": "Портативний", + "ControllerSettingsInputDevice": "Пристрій введення", + "ControllerSettingsRefresh": "Оновити", + "ControllerSettingsDeviceDisabled": "Вимкнено", + "ControllerSettingsControllerType": "Тип контролера", + "ControllerSettingsControllerTypeHandheld": "Портативний", + "ControllerSettingsControllerTypeProController": "Контролер Pro", + "ControllerSettingsControllerTypeJoyConPair": "Обидва JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Лівий JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Правий JoyCon", + "ControllerSettingsProfile": "Профіль", + "ControllerSettingsProfileDefault": "Типовий", + "ControllerSettingsLoad": "Завантажити", + "ControllerSettingsAdd": "Додати", + "ControllerSettingsRemove": "Видалити", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Панель направлення", + "ControllerSettingsDPadUp": "Вгору", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Вліво", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Лівий джойстик", + "ControllerSettingsRStick": "Правий джойстик", + "ControllerSettingsTriggersLeft": "Тригери ліворуч", + "ControllerSettingsTriggersRight": "Тригери праворуч", + "ControllerSettingsTriggersButtonsLeft": "Кнопки тригерів ліворуч", + "ControllerSettingsTriggersButtonsRight": "Кнопки тригерів праворуч", + "ControllerSettingsTriggers": "Тригери", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Кнопки ліворуч", + "ControllerSettingsExtraButtonsRight": "Кнопки праворуч", + "ControllerSettingsMisc": "Різне", + "ControllerSettingsTriggerThreshold": "Поріг спрацьовування:", + "ControllerSettingsMotion": "Рух", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Використовувати рух, сумісний з CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контролера:", + "ControllerSettingsMotionMirrorInput": "Дзеркальний вхід", + "ControllerSettingsMotionRightJoyConSlot": "Правий слот JoyCon:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чутливість гіроскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", + "ControllerSettingsSave": "Зберегти", + "ControllerSettingsClose": "Закрити", + "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", + "UserProfilesSaveProfileName": "Зберегти ім'я профілю", + "UserProfilesChangeProfileImage": "Змінити зображення профілю", + "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", + "UserProfilesAddNewProfile": "Створити профіль", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Закрити", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Вибір зображення профілю", + "ProfileImageSelectionHeader": "Виберіть зображення профілю", + "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", + "ProfileImageSelectionImportImage": "Імпорт файлу зображення", + "ProfileImageSelectionSelectAvatar": "Виберіть аватар прошивки ", + "InputDialogTitle": "Діалог введення", + "InputDialogOk": "Гаразд", + "InputDialogCancel": "Скасувати", + "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", + "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", + "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", + "AvatarChoose": "Вибрати", + "AvatarSetBackgroundColor": "Встановити колір фону", + "AvatarClose": "Закрити", + "ControllerSettingsLoadProfileToolTip": "Завантажити профіль", + "ControllerSettingsAddProfileToolTip": "Додати профіль", + "ControllerSettingsRemoveProfileToolTip": "Видалити профіль", + "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", + "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", + "MenuBarFileToolsHideUi": "Сховати інтерфейс", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Перемкнути вибране", + "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", + "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Темна", + "SettingsTabGeneralThemeBaseStyleLight": "Світла", + "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", + "ButtonBrowse": "Огляд", + "ControllerSettingsConfigureGeneral": "Налаштування", + "ControllerSettingsRumble": "Вібрація", + "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", + "ControllerSettingsRumbleWeakMultiplier": "Множник слабкої вібрації", + "DialogMessageSaveNotAvailableMessage": "Немає збережених даних для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Хочете створити дані збереження для цієї гри?", + "DialogConfirmationTitle": "Ryujinx - Підтвердження", + "DialogUpdaterTitle": "Ryujinx - Програма оновлення", + "DialogErrorTitle": "Ryujinx - Помилка", + "DialogWarningTitle": "Ryujinx - Попередження", + "DialogExitTitle": "Ryujinx - Вихід", + "DialogErrorMessage": "У Ryujinx сталася помилка", + "DialogExitMessage": "Ви впевнені, що бажаєте закрити Ryujinx?", + "DialogExitSubMessage": "Усі незбережені дані буде втрачено!", + "DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}", + "DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}", + "FolderDialogExtractTitle": "Виберіть папку для видобування", + "DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Екстрактор розділів NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.", + "DialogNcaExtractionCheckLogErrorMessage": "Помилка видобування. Прочитайте файл журналу для отримання додаткової інформації.", + "DialogNcaExtractionSuccessMessage": "Видобування успішно завершено.", + "DialogUpdaterConvertFailedMessage": "Не вдалося конвертувати поточну версію Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Скасування оновлення!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Ви вже використовуєте останню версію Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Під час спроби отримати інформацію про випуск із GitHub Release сталася помилка. Це може бути спричинено, якщо новий випуск компілюється GitHub Actions. Повторіть спробу через кілька хвилин.", + "DialogUpdaterConvertFailedGithubMessage": "Не вдалося конвертувати отриману версію Ryujinx із випуску Github.", + "DialogUpdaterDownloadingMessage": "Завантаження оновлення...", + "DialogUpdaterExtractionMessage": "Видобування оновлення...", + "DialogUpdaterRenamingMessage": "Перейменування оновлення...", + "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", + "DialogUpdaterCompleteMessage": "Оновлення завершено!", + "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", + "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", + "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", + "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", + "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", + "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Будь ласка, завантажте Ryujinx на https://ryujinx.org/, якщо ви шукаєте підтримувану версію.", + "DialogRestartRequiredMessage": "Потрібен перезапуск", + "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", + "DialogThemeRestartSubMessage": "Ви хочете перезапустити", + "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", + "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", + "DialogControllerAppletTitle": "Аплет контролера", + "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", + "DialogErrorAppletErrorExceptionMessage": "Помилка показу діалогового вікна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля отримання додаткової інформації про те, як виправити цю помилку, дотримуйтесь нашого посібника з налаштування.", + "DialogUserErrorDialogTitle": "Помилка Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Під час отримання інформації з API сталася помилка.", + "DialogAmiiboApiConnectErrorMessage": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.", + "DialogProfileInvalidProfileErrorMessage": "Профіль {0} несумісний із поточною системою конфігурації вводу.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Стандартний профіль не можна перезаписати", + "DialogProfileDeleteProfileTitle": "Видалення профілю", + "DialogProfileDeleteProfileMessage": "Цю дію неможливо скасувати. Ви впевнені, що бажаєте продовжити?", + "DialogWarning": "Увага", + "DialogPPTCDeletionMessage": "Ви збираєтеся видалити кеш PPTC для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogPPTCDeletionErrorMessage": "Помилка очищення кешу PPTC на {0}: {1}", + "DialogShaderDeletionMessage": "Ви збираєтеся видалити кеш шейдерів для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogShaderDeletionErrorMessage": "Помилка очищення кешу шейдерів на {0}: {1}", + "DialogRyujinxErrorMessage": "У Ryujinx сталася помилка", + "DialogInvalidTitleIdErrorMessage": "Помилка інтерфейсу: вибрана гра не мала дійсного ідентифікатора назви", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Дійсна прошивка системи не знайдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Встановити прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Буде встановлено версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЦе замінить поточну версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nВи хочете продовжити?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", + "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", + "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", + "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", + "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Ви увімкнули скидання шейдерів, призначений лише для розробників.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути скидання шейдерів. Ви хочете вимкнути скидання шейдерів зараз?", + "DialogLoadAppGameAlreadyLoadedMessage": "Гру вже завантажено", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Зупиніть емуляцію або закрийте емулятор перед запуском іншої гри.", + "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Особливості", + "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", + "CommonAuto": "Авто", + "CommonOff": "Вимкнути", + "CommonOn": "Увімкнути", + "InputDialogYes": "Так", + "InputDialogNo": "Ні", + "DialogProfileInvalidProfileNameErrorMessage": "Ім'я файлу містить неприпустимі символи. Будь ласка, спробуйте ще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продовжити", + "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", + "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", + "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", + "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", + "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", + "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", + "AboutRyujinxAboutTitle": "Про програму:", + "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", + "AboutRyujinxMaintainersTitle": "Підтримується:", + "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", + "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", + "AmiiboSeriesLabel": "Серія Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканувати", + "AmiiboOptionsShowAllLabel": "Показати всі Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хитрість: Використовувати випадковий тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Увімкнено", + "DlcManagerTableHeadingTitleIdLabel": "ID заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Шлях до контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Повний шлях", + "DlcManagerRemoveAllButton": "Видалити все", + "DlcManagerEnableAllButton": "Увімкнути всі", + "DlcManagerDisableAllButton": "Вимкнути всі", + "MenuBarOptionsChangeLanguage": "Змінити мову", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Сортувати", + "CommonShowNames": "Показати назви", + "CommonFavorite": "Вибрані", + "OrderAscending": "За зростанням", + "OrderDescending": "За спаданням", + "SettingsTabGraphicsFeatures": "Функції та вдосконалення", + "ErrorWindowTitle": "Вікно помилок", + "ToggleDiscordTooltip": "Виберіть, чи відображати Ryujinx у вашій «поточній грі» в Discord", + "AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку", + "AddGameDirTooltip": "Додати каталог гри до списку", + "RemoveGameDirTooltip": "Видалити вибраний каталог гри", + "CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора", + "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", + "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", + "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", + "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", + "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", + "RegionTooltip": "Змінити регіон системи", + "LanguageTooltip": "Змінити мову системи", + "TimezoneTooltip": "Змінити часовий пояс системи", + "TimeTooltip": "Змінити час системи", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", + "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", + "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", + "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", + "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", + "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", + "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", + "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", + "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", + "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", + "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", + "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", + "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", + "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", + "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", + "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", + "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", + "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", + "InfoLogTooltip": "Друкує повідомлення інформаційного журналу на консолі. Не впливає на продуктивність.", + "WarnLogTooltip": "Друкує повідомлення журналу попереджень у консолі. Не впливає на продуктивність.", + "ErrorLogTooltip": "Друкує повідомлення журналу помилок у консолі. Не впливає на продуктивність.", + "TraceLogTooltip": "Друкує повідомлення журналу трасування на консолі. Не впливає на продуктивність.", + "GuestLogTooltip": "Друкує повідомлення журналу гостей у консолі. Не впливає на продуктивність.", + "FileAccessLogTooltip": "Друкує повідомлення журналу доступу до файлів у консолі.", + "FSAccessLogModeTooltip": "Вмикає виведення журналу доступу до FS на консоль. Можливі режими 0-3", + "DeveloperOptionTooltip": "Використовуйте з обережністю", + "OpenGlLogLevel": "Потрібно увімкнути відповідні рівні журналу", + "DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.", + "LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch", + "LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження", + "OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx", + "OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали", + "ExitTooltip": "Виходить з Ryujinx", + "OpenSettingsTooltip": "Відкриває вікно налаштувань", + "OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів", + "StopEmulationTooltip": "Зупиняє емуляцію поточної гри та повертається до вибору гри", + "CheckUpdatesTooltip": "Перевіряє наявність оновлень для Ryujinx", + "OpenAboutTooltip": "Відкриває вікно «Про програму».", + "GridSize": "Розмір сітки", + "GridSizeTooltip": "Змінити розмір елементів сітки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальська (Бразилія)", + "AboutRyujinxContributorsButtonHeader": "Переглянути всіх співавторів", + "SettingsTabSystemAudioVolume": "Гучність: ", + "AudioVolumeTooltip": "Змінити гучність звуку", + "SettingsTabSystemEnableInternetAccess": "Гостьовий доступ до Інтернету/режим LAN", + "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", + "GameListContextMenuManageCheatToolTip": "Керування читами", + "GameListContextMenuManageCheat": "Керування читами", + "ControllerSettingsStickRange": "Діапазон:", + "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", + "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудіо", + "SettingsTabNetwork": "Мережа", + "SettingsTabNetworkConnection": "Підключення до мережі", + "SettingsTabCpuCache": "Кеш ЦП", + "SettingsTabCpuMemory": "Пам'ять ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", + "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", + "IconSize": "Розмір значка", + "IconSizeTooltip": "Змінити розмір значків гри", + "MenuBarOptionsShowConsole": "Показати консоль", + "ShaderCachePurgeError": "Помилка очищення кешу шейдера {0}: {1}", + "UserErrorNoKeys": "Ключі не знайдено", + "UserErrorNoFirmware": "Прошивка не знайдена", + "UserErrorFirmwareParsingFailed": "Помилка аналізу прошивки", + "UserErrorApplicationNotFound": "Додаток не знайдено", + "UserErrorUnknown": "Невідома помилка", + "UserErrorUndefined": "Невизначена помилка", + "UserErrorNoKeysDescription": "Ryujinx не вдалося знайти ваш файл «prod.keys».", + "UserErrorNoFirmwareDescription": "Ryujinx не вдалося знайти встановлену прошивку", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не вдалося знайти дійсний додаток за вказаним шляхом", + "UserErrorUnknownDescription": "Сталася невідома помилка!", + "UserErrorUndefinedDescription": "Сталася невизначена помилка! Цього не повинно статися, зверніться до розробника!", + "OpenSetupGuideMessage": "Відкрити посібник із налаштування", + "NoUpdate": "Немає оновлень", + "TitleUpdateVersionLabel": "Версія {0} - {1}", + "RyujinxInfo": "Ryujin x - Інформація", + "RyujinxConfirm": "Ryujinx - Підтвердження", + "FileDialogAllTypes": "Всі типи", + "Never": "Ніколи", + "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", + "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", + "SoftwareKeyboard": "Програмна клавіатура", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", + "UpdaterRenaming": "Перейменування старих файлів...", + "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", + "UpdaterAddingFiles": "Додавання нових файлів...", + "UpdaterExtracting": "Видобування оновлення...", + "UpdaterDownloading": "Завантаження оновлення...", + "Game": "Гра", + "Docked": "Док-станція", + "Handheld": "Портативний", + "ConnectionError": "Помилка з'єднання.", + "AboutPageDeveloperListMore": "{0} та інші...", + "ApiError": "Помилка API.", + "LoadingHeading": "Завантаження {0}", + "CompilingPPTC": "Компіляція PTC", + "CompilingShaders": "Компіляція шейдерів", + "AllKeyboards": "Всі клавіатури", + "OpenFileDialogTitle": "Виберіть підтримуваний файл для відкриття", + "OpenFolderDialogTitle": "Виберіть теку з розпакованою грою", + "AllSupportedFormats": "Усі підтримувані формати", + "RyujinxUpdater": "Програма оновлення Ryujinx", + "SettingsTabHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:", + "SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:", + "SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:", + "ControllerMotionTitle": "Налаштування керування рухом", + "ControllerRumbleTitle": "Налаштування вібрації", + "SettingsSelectThemeFileDialogTitle": "Виберіть файл теми", + "SettingsXamlThemeFile": "Файл теми Xaml", + "AvatarWindowTitle": "Керування обліковими записами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Невідомо", + "Usage": "Використання", + "Writable": "Можливість запису", + "SelectDlcDialogTitle": "Виберіть файли DLC", + "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "UserProfileWindowTitle": "Менеджер профілів користувачів", + "CheatWindowTitle": "Менеджер читів", + "DlcWindowTitle": "Менеджер вмісту для завантаження", + "UpdateWindowTitle": "Менеджер оновлення назв", + "CheatWindowHeading": "Коди доступні для {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "UserProfilesEditProfile": "Редагувати вибране", + "Cancel": "Скасувати", + "Save": "Зберегти", + "Discard": "Скасувати", + "UserProfilesSetProfileImage": "Встановити зображення профілю", + "UserProfileEmptyNameError": "Назва обов'язкова", + "UserProfileNoImageError": "Зображення профілю обов'язкове", + "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", + "UserProfilesName": "Ім'я", + "UserProfilesUserId": "ID користувача:", + "SettingsTabGraphicsBackend": "Графічний сервер", + "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", + "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", + "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", + "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", + "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", + "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", + "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", + "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", + "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", + "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", + "SettingsEnableMacroHLE": "Увімкнути макрос HLE", + "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Гуч", + "UserProfilesManageSaves": "Керувати збереженнями", + "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", + "IrreversibleActionNote": "Цю дію не можна скасувати.", + "SaveManagerHeading": "Керувати збереженнями для {0}", + "SaveManagerTitle": "Менеджер збереження", + "Name": "Назва", + "Size": "Розмір", + "Search": "Пошук", + "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", + "Recover": "Відновити", + "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edit User", + "UserEditorTitleCreate": "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN features", + "NetworkInterfaceDefault": "Default", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json new file mode 100644 index 00000000..d09a80ec --- /dev/null +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -0,0 +1,656 @@ +{ + "Language": "简体中文", + "MenuBarFileOpenApplet": "打开小程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", + "SettingsTabInputDirectMouseAccess": "直通鼠标操作", + "SettingsTabSystemMemoryManagerMode": "内存管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "软件", + "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", + "MenuBarFile": "文件", + "MenuBarFileOpenFromFile": "加载文件", + "MenuBarFileOpenUnpacked": "加载解包后的游戏", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", + "MenuBarFileOpenLogsFolder": "打开日志文件夹", + "MenuBarFileExit": "退出", + "MenuBarOptions": "选项", + "MenuBarOptionsToggleFullscreen": "切换全屏", + "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", + "MenuBarOptionsStopEmulation": "停止模拟", + "MenuBarOptionsSettings": "设置", + "MenuBarOptionsManageUserProfiles": "管理用户账户", + "MenuBarActions": "操作", + "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", + "MenuBarActionsScanAmiibo": "扫描 Amiibo", + "MenuBarTools": "工具", + "MenuBarToolsInstallFirmware": "安装固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", + "MenuBarToolsManageFileTypes": "管理文件扩展名", + "MenuBarToolsInstallFileTypes": "关联文件扩展名", + "MenuBarToolsUninstallFileTypes": "取消关联扩展名", + "MenuBarHelp": "帮助", + "MenuBarHelpCheckForUpdates": "检查更新", + "MenuBarHelpAbout": "关于", + "MenuSearch": "搜索…", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "图标", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "制作商", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "游玩时长", + "GameListHeaderLastPlayed": "最近游玩", + "GameListHeaderFileExtension": "扩展名", + "GameListHeaderFileSize": "大小", + "GameListHeaderPath": "路径", + "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开系统目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", + "GameListContextMenuManageTitleUpdates": "管理游戏更新", + "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuCacheManagement": "缓存管理", + "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含游戏着色器缓存的目录", + "GameListContextMenuExtractData": "提取数据", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", + "GameListContextMenuExtractDataLogo": "图标", + "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", + "StatusBarSystemVersion": "系统版本:{0}", + "LinuxVmMaxMapCountDialogTitle": "检测到内存映射的限制过低", + "LinuxVmMaxMapCountDialogTextPrimary": "你想要将 vm.max_map_count 的值增加到 {0} 吗", + "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会试图创建超过当前允许的内存映射数量。当超过此限制时,Ryujinx会立即崩溃。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,关闭后重置", + "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", + "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会试图创建超过当前允许的内存映射量。当大于此限制时,Ryujinx 会立即崩溃。\n\n你可以手动增加内存映射限制或者安装 pkexec,它可以辅助Ryujinx解决该问题。", + "Settings": "设置", + "SettingsTabGeneral": "用户界面", + "SettingsTabGeneralGeneral": "常规", + "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", + "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", + "SettingsTabGeneralHideCursorNever": "从不", + "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", + "SettingsTabGeneralHideCursorAlways": "始终", + "SettingsTabGeneralGameDirectories": "游戏目录", + "SettingsTabGeneralAdd": "添加", + "SettingsTabGeneralRemove": "删除", + "SettingsTabSystem": "系统", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系统区域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美国", + "SettingsTabSystemSystemRegionEurope": "欧洲", + "SettingsTabSystemSystemRegionAustralia": "澳大利亚", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韩国", + "SettingsTabSystemSystemRegionTaiwan": "台湾地区", + "SettingsTabSystemSystemLanguage": "系统语言:", + "SettingsTabSystemSystemLanguageJapanese": "日语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", + "SettingsTabSystemSystemLanguageFrench": "法语", + "SettingsTabSystemSystemLanguageGerman": "德语", + "SettingsTabSystemSystemLanguageItalian": "意大利语", + "SettingsTabSystemSystemLanguageSpanish": "西班牙语", + "SettingsTabSystemSystemLanguageChinese": "简体中文", + "SettingsTabSystemSystemLanguageKorean": "韩语", + "SettingsTabSystemSystemLanguageDutch": "荷兰语", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", + "SettingsTabSystemSystemLanguageRussian": "俄语", + "SettingsTabSystemSystemLanguageTaiwanese": "繁体中文(台湾)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)", + "SettingsTabSystemSystemTimeZone": "系统时区:", + "SettingsTabSystemSystemTime": "系统时钟:", + "SettingsTabSystemEnableVsync": "启用垂直同步", + "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", + "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频后端:", + "SettingsTabSystemAudioBackendDummy": "无", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "音频输入/输出", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": "(会引起模拟器不稳定)", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", + "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", + "SettingsTabGraphics": "图形", + "SettingsTabGraphicsAPI": "图形 API", + "SettingsTabGraphicsEnableShaderCache": "启用着色器缓存", + "SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自动", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "分辨率缩放:", + "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "宽高比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以适应窗口", + "SettingsTabGraphicsDeveloperOptions": "开发者选项", + "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", + "SettingsTabLogging": "日志", + "SettingsTabLoggingLogging": "日志", + "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", + "SettingsTabLoggingEnableStubLogs": "启用 Stub 日志", + "SettingsTabLoggingEnableInfoLogs": "启用信息日志", + "SettingsTabLoggingEnableWarningLogs": "启用警告日志", + "SettingsTabLoggingEnableErrorLogs": "启用错误日志", + "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", + "SettingsTabLoggingEnableGuestLogs": "启用来宾日志", + "SettingsTabLoggingEnableFsAccessLogs": "启用访问日志", + "SettingsTabLoggingFsGlobalAccessLogMode": "全局访问日志模式:", + "SettingsTabLoggingDeveloperOptions": "开发者选项", + "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低性能", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", + "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "启用调试日志", + "SettingsTabInput": "输入", + "SettingsTabInputEnableDockedMode": "主机模式", + "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", + "SettingsButtonSave": "保存", + "SettingsButtonClose": "取消", + "SettingsButtonOk": "确定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "应用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌机模式", + "ControllerSettingsInputDevice": "输入设备", + "ControllerSettingsRefresh": "刷新", + "ControllerSettingsDeviceDisabled": "关闭", + "ControllerSettingsControllerType": "手柄类型", + "ControllerSettingsControllerTypeHandheld": "掌机", + "ControllerSettingsControllerTypeProController": "Pro 手柄", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon 组合", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "预设", + "ControllerSettingsProfileDefault": "默认布局", + "ControllerSettingsLoad": "加载", + "ControllerSettingsAdd": "新建", + "ControllerSettingsRemove": "删除", + "ControllerSettingsButtons": "按键", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向键", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按下摇杆", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "摇杆", + "ControllerSettingsStickInvertXAxis": "反转 X 轴方向", + "ControllerSettingsStickInvertYAxis": "反转 Y 轴方向", + "ControllerSettingsStickDeadzone": "死区:", + "ControllerSettingsLStick": "左摇杆", + "ControllerSettingsRStick": "右摇杆", + "ControllerSettingsTriggersLeft": "左扳机", + "ControllerSettingsTriggersRight": "右扳机", + "ControllerSettingsTriggersButtonsLeft": "左扳机键", + "ControllerSettingsTriggersButtonsRight": "右扳机键", + "ControllerSettingsTriggers": "扳机", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左背键", + "ControllerSettingsExtraButtonsRight": "右背键", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "扳机阈值:", + "ControllerSettingsMotion": "体感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", + "ControllerSettingsMotionControllerSlot": "手柄:", + "ControllerSettingsMotionMirrorInput": "镜像操作", + "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", + "ControllerSettingsMotionServerHost": "服务器Host:", + "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", + "ControllerSettingsSave": "保存", + "ControllerSettingsClose": "关闭", + "UserProfilesSelectedUserProfile": "选择的用户账户:", + "UserProfilesSaveProfileName": "保存名称", + "UserProfilesChangeProfileImage": "更换头像", + "UserProfilesAvailableUserProfiles": "现有账户:", + "UserProfilesAddNewProfile": "新建账户", + "UserProfilesDelete": "删除", + "UserProfilesClose": "关闭", + "ProfileNameSelectionWatermark": "选择昵称", + "ProfileImageSelectionTitle": "选择头像", + "ProfileImageSelectionHeader": "选择合适的头像图片", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", + "ProfileImageSelectionImportImage": "导入图像文件", + "ProfileImageSelectionSelectAvatar": "选择系统头像", + "InputDialogTitle": "输入对话框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "选择用户名称", + "InputDialogAddNewProfileHeader": "请输入账户名称", + "InputDialogAddNewProfileSubtext": "(最大长度:{0})", + "AvatarChoose": "选择头像", + "AvatarSetBackgroundColor": "设置背景色", + "AvatarClose": "关闭", + "ControllerSettingsLoadProfileToolTip": "加载预设", + "ControllerSettingsAddProfileToolTip": "新增预设", + "ControllerSettingsRemoveProfileToolTip": "删除预设", + "ControllerSettingsSaveProfileToolTip": "保存预设", + "MenuBarFileToolsTakeScreenshot": "保存截图", + "MenuBarFileToolsHideUi": "隐藏界面", + "GameListContextMenuRunApplication": "运行应用", + "GameListContextMenuToggleFavorite": "收藏", + "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", + "SettingsTabGeneralTheme": "主题", + "SettingsTabGeneralThemeCustomTheme": "自选主题路径", + "SettingsTabGeneralThemeBaseStyle": "主题色调", + "SettingsTabGeneralThemeBaseStyleDark": "暗黑", + "SettingsTabGeneralThemeBaseStyleLight": "浅色", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", + "ButtonBrowse": "浏览", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震动", + "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", + "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", + "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", + "DialogConfirmationTitle": "Ryujinx - 设置", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 错误", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 关闭", + "DialogErrorMessage": "Ryujinx 发生错误", + "DialogExitMessage": "是否关闭 Ryujinx?", + "DialogExitSubMessage": "未保存的进度将会丢失!", + "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错:{0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错:{0}", + "FolderDialogExtractTitle": "选择要解压到的文件夹", + "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过一会再试。", + "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下载新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在删除旧文件...", + "DialogUpdaterAddingFilesMessage": "安装更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", + "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", + "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", + "DialogUpdaterNoInternetMessage": "没有连接到互联网", + "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", + "DialogRestartRequiredMessage": "需要重启模拟器", + "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", + "DialogThemeRestartSubMessage": "您是否要重启?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", + "DialogFirmwareInstalledMessage": "已安装固件 {0}", + "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", + "DialogInstallFileTypesErrorMessage": "关联文件类型失败。", + "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", + "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败。", + "DialogOpenSettingsWindowLabel": "打开设置窗口", + "DialogControllerAppletTitle": "控制器小窗口", + "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", + "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", + "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", + "DialogProfileInvalidProfileErrorMessage": "预设 {0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", + "DialogProfileDeleteProfileTitle": "删除预设", + "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除位于 {0} 的 PPTC 缓存时出错:{1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除位于 {0} 的着色器缓存时出错:{1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", + "DialogInvalidTitleIdErrorMessage": "UI错误:所选游戏没有有效的标题ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径 {0} 找不到有效的系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "固件 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", + "DialogUserProfileUnsavedChangesTitle": "警告 - 未保存的更改", + "DialogUserProfileUnsavedChangesMessage": "您为该用户做出的部分改动尚未保存。", + "DialogUserProfileUnsavedChangesSubMessage": "是否舍弃这些改动?", + "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", + "DialogLoadNcaErrorMessage": "{0}. 错误的文件:{1}", + "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", + "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", + "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "多线程图形后端:", + "CommonAuto": "自动(推荐)", + "CommonOff": "关闭", + "CommonOn": "打开", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", + "MenuBarOptionsPauseEmulation": "暂停", + "MenuBarOptionsResumeEmulation": "继续", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 官网。", + "AboutDisclaimerMessage": "Ryujinx 以任何方式与 Nintendo™ 及其任何商业伙伴都没有关联", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", + "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", + "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", + "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", + "AboutRyujinxAboutTitle": "关于:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", + "AboutRyujinxMaintainersTitle": "由以下作者维护:", + "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "扫描", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", + "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", + "DlcManagerTableHeadingEnabledLabel": "启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏ID", + "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", + "DlcManagerTableHeadingFullPathLabel": "完整路径", + "DlcManagerRemoveAllButton": "全部删除", + "DlcManagerEnableAllButton": "全部启用", + "DlcManagerDisableAllButton": "全部禁用", + "MenuBarOptionsChangeLanguage": "更改语言", + "MenuBarShowFileTypes": "主页显示的文件类型", + "CommonSort": "排序", + "CommonShowNames": "显示名称", + "CommonFavorite": "收藏", + "OrderAscending": "从小到大", + "OrderDescending": "从大到小", + "SettingsTabGraphicsFeatures": "功能与增强", + "ErrorWindowTitle": "错误窗口", + "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", + "AddGameDirBoxTooltip": "输入要添加的游戏目录", + "AddGameDirTooltip": "添加游戏目录到列表中", + "RemoveGameDirTooltip": "移除选中的目录", + "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", + "CustomThemePathTooltip": "自定义主题的目录", + "CustomThemeBrowseTooltip": "查找自定义主题", + "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型。", + "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", + "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", + "RegionTooltip": "更改系统区域", + "LanguageTooltip": "更改系统语言", + "TimezoneTooltip": "更改系统时区", + "TimeTooltip": "更改系统时钟", + "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定,就请保持开启状态。", + "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度。\n如不确定,就请保持开启状态。", + "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。\n如不确定,就请保持开启状态。", + "AudioBackendTooltip": "默认推荐SDL2,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端。", + "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗。", + "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢。", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译效率更高。", + "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,使得即时编译效率更高。\nRyujinx 可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)。", + "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译。在可用的情况下能大幅提高性能。但目前可能不稳定。", + "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", + "GalThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", + "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿。", + "ResolutionScaleTooltip": "缩放渲染的分辨率", + "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG。", + "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", + "AspectRatioTooltip": "渲染窗口的宽高比。", + "ShaderDumpPathTooltip": "转储图形着色器的路径", + "FileLogTooltip": "保存日志文件到硬盘。不会影响性能。", + "StubLogTooltip": "在控制台中打印 stub 日志消息。不影响性能。", + "InfoLogTooltip": "在控制台中打印信息日志消息。不影响性能。", + "WarnLogTooltip": "在控制台中打印警告日志消息。不影响性能。", + "ErrorLogTooltip": "打印控制台中的错误日志消息。不影响性能。", + "TraceLogTooltip": "在控制台中打印跟踪日志消息。不影响性能。", + "GuestLogTooltip": "在控制台中打印访客日志消息。不影响性能。", + "FileAccessLogTooltip": "在控制台中打印文件访问日志信息。", + "FSAccessLogModeTooltip": "启用访问日志输出到控制台。可能的模式为 0-3", + "DeveloperOptionTooltip": "谨慎使用", + "OpenGlLogLevel": "需要打开适当的日志等级", + "DebugLogTooltip": "记录Debug消息", + "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", + "OpenRyujinxLogsTooltip": "打开日志存放的目录", + "ExitTooltip": "关闭 Ryujinx", + "OpenSettingsTooltip": "打开设置窗口", + "OpenProfileManagerTooltip": "打开用户账户管理界面", + "StopEmulationTooltip": "停止运行当前游戏并回到主界面", + "CheckUpdatesTooltip": "检查 Ryujinx 新版本", + "OpenAboutTooltip": "打开“关于”窗口", + "GridSize": "网格尺寸", + "GridSizeTooltip": "调整网格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", + "AboutRyujinxContributorsButtonHeader": "查看所有参与者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "调节音量", + "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", + "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实的 Switch 连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "范围:", + "DialogStopEmulationTitle": "Ryujinx - 停止模拟", + "DialogStopEmulationMessage": "是否确定停止模拟?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音频", + "SettingsTabNetwork": "网络", + "SettingsTabNetworkConnection": "网络连接", + "SettingsTabCpuCache": "CPU 缓存", + "SettingsTabCpuMemory": "CPU 内存", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已禁用!", + "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", + "ControllerSettingsRotate90": "顺时针旋转 90°", + "IconSize": "图标尺寸", + "IconSizeTooltip": "更改游戏图标大小", + "MenuBarOptionsShowConsole": "显示控制台", + "ShaderCachePurgeError": "清除着色器缓存时出错:{0}: {1}", + "UserErrorNoKeys": "找不到密钥", + "UserErrorNoFirmware": "找不到固件", + "UserErrorFirmwareParsingFailed": "固件解析错误", + "UserErrorApplicationNotFound": "找不到应用程序", + "UserErrorUnknown": "未知错误", + "UserErrorUndefined": "未定义错误", + "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于使用了过旧的密钥。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", + "UserErrorUnknownDescription": "发生未知错误!", + "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", + "OpenSetupGuideMessage": "打开设置教程", + "NoUpdate": "无更新", + "TitleUpdateVersionLabel": "版本 {0}", + "RyujinxInfo": "Ryujinx - 信息", + "RyujinxConfirm": "Ryujinx - 确认", + "FileDialogAllTypes": "全部类型", + "Never": "从不", + "SwkbdMinCharacters": "至少应为 {0} 个字长", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", + "SoftwareKeyboard": "软件键盘", + "SoftwareKeyboardModeNumbersOnly": "只接受数字", + "SoftwareKeyboardModeAlphabet": "只接受非中日韩文字", + "SoftwareKeyboardModeASCII": "只接受 ASCII 符号", + "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", + "UpdaterRenaming": "正在删除旧文件...", + "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", + "UpdaterAddingFiles": "安装更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下载新版本中...", + "Game": "游戏", + "Docked": "主机模式", + "Handheld": "掌机模式", + "ConnectionError": "连接错误。", + "AboutPageDeveloperListMore": "{0} 等开发者...", + "ApiError": "API错误。", + "LoadingHeading": "正在启动 {0}", + "CompilingPPTC": "编译PPTC缓存中", + "CompilingShaders": "编译着色器中", + "AllKeyboards": "所有键盘", + "OpenFileDialogTitle": "选择一个支持的文件以打开", + "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", + "AllSupportedFormats": "所有支持的格式", + "RyujinxUpdater": "Ryujinx 更新程序", + "SettingsTabHotkeys": "快捷键", + "SettingsTabHotkeysHotkeys": "键盘快捷键", + "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "截屏:", + "SettingsTabHotkeysShowUiHotkey": "隐藏 界面:", + "SettingsTabHotkeysPauseHotkey": "暂停:", + "SettingsTabHotkeysToggleMuteHotkey": "静音:", + "ControllerMotionTitle": "体感操作设置", + "ControllerRumbleTitle": "震动设置", + "SettingsSelectThemeFileDialogTitle": "选择主题文件", + "SettingsXamlThemeFile": "Xaml 主题文件", + "AvatarWindowTitle": "管理账户 - 头像", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "扫描可获得", + "Writable": "可写入", + "SelectDlcDialogTitle": "选择 DLC 文件", + "SelectUpdateDialogTitle": "选择更新文件", + "UserProfileWindowTitle": "管理用户账户", + "CheatWindowTitle": "金手指管理器", + "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", + "UpdateWindowTitle": "游戏更新管理器", + "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", + "BuildId": "游戏版本ID:", + "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", + "UserProfilesEditProfile": "编辑选中账户", + "Cancel": "取消", + "Save": "保存", + "Discard": "返回", + "UserProfilesSetProfileImage": "选择头像", + "UserProfileEmptyNameError": "必须输入名称", + "UserProfileNoImageError": "请选择您的头像", + "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", + "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "用户ID:", + "SettingsTabGraphicsBackend": "图形后端", + "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", + "SettingsEnableTextureRecompression": "启用纹理重压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU 开启。\n如果您不确定,请保持此项关闭。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan API 使用的显卡。\n此选项不会影响 OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项。", + "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", + "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", + "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", + "SettingsTabHotkeysVolumeUpHotkey": "音量加:", + "SettingsTabHotkeysVolumeDownHotkey": "音量减:", + "SettingsEnableMacroHLE": "启用 HLE 宏", + "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项开启。", + "SettingsEnableColorSpacePassthrough": "颜色空间穿透", + "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 后端在不指定颜色空间的情况下传递颜色信息。对于具有宽色域显示器的用户来说,这可能会以颜色正确性为代价,产生更鲜艳的颜色。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理存档", + "DeleteUserSave": "确定删除这个游戏的存档吗?", + "IrreversibleActionNote": "删除后不可恢复。", + "SaveManagerHeading": "管理 {0} ({1}) 的存档", + "SaveManagerTitle": "存档管理器", + "Name": "名称", + "Size": "大小", + "Search": "搜索", + "UserProfilesRecoverLostAccounts": "恢复丢失的账户", + "Recover": "恢复", + "UserProfilesRecoverHeading": "找到了这些用户的存档数据", + "UserProfilesRecoverEmptyList": "没有可以恢复的配置文件", + "GraphicsAATooltip": "将抗锯齿使用到游戏渲染中", + "GraphicsAALabel": "抗锯齿:", + "GraphicsScalingFilterLabel": "缩放过滤:", + "GraphicsScalingFilterTooltip": "对帧缓冲区进行缩放", + "GraphicsScalingFilterLevelLabel": "等级", + "GraphicsScalingFilterLevelTooltip": "设置缩放过滤级别", + "SmaaLow": "SMAA 低质量", + "SmaaMedium": "SMAA 中质量", + "SmaaHigh": "SMAA 高质量", + "SmaaUltra": "SMAA 极致质量", + "UserEditorTitle": "编辑用户", + "UserEditorTitleCreate": "创建用户", + "SettingsTabNetworkInterface": "网络接口:", + "NetworkInterfaceTooltip": "用于局域网功能的网络接口", + "NetworkInterfaceDefault": "默认", + "PackagingShaders": "整合着色器中", + "AboutChangelogButton": "在Github上查看更新日志", + "AboutChangelogButtonTooltipMessage": "点击这里在您的默认浏览器中打开此版本的更新日志。" +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json new file mode 100644 index 00000000..a2f59f60 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -0,0 +1,656 @@ +{ + "Language": "英文 (美國)", + "MenuBarFileOpenApplet": "開啟 Applet 應用程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "開啟獨立的Mii修改器應用程序", + "SettingsTabInputDirectMouseAccess": "滑鼠直接操作", + "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體", + "SettingsTabSystemMemoryManagerModeHost": "主機模式 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "主機略過檢查模式 (最快, 但不安全)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor", + "MenuBarFile": "_檔案", + "MenuBarFileOpenFromFile": "_載入檔案", + "MenuBarFileOpenUnpacked": "載入_已解開封裝的遊戲", + "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", + "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", + "MenuBarFileExit": "_退出", + "MenuBarOptions": "選項", + "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", + "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", + "MenuBarOptionsStopEmulation": "停止模擬", + "MenuBarOptionsSettings": "_設定", + "MenuBarOptionsManageUserProfiles": "_管理使用者帳戶", + "MenuBarActions": "_動作", + "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", + "MenuBarActionsScanAmiibo": "掃描 Amiibo", + "MenuBarTools": "_工具", + "MenuBarToolsInstallFirmware": "安裝韌體", + "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", + "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", + "MenuBarToolsManageFileTypes": "管理檔案類型", + "MenuBarToolsInstallFileTypes": "註冊檔案類型", + "MenuBarToolsUninstallFileTypes": "取消註冊檔案類型", + "MenuBarHelp": "幫助", + "MenuBarHelpCheckForUpdates": "檢查更新", + "MenuBarHelpAbout": "關於", + "MenuSearch": "搜尋...", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "圖示", + "GameListHeaderApplication": "名稱", + "GameListHeaderDeveloper": "開發人員", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "遊玩時數", + "GameListHeaderLastPlayed": "最近遊玩", + "GameListHeaderFileExtension": "副檔名", + "GameListHeaderFileSize": "檔案大小", + "GameListHeaderPath": "路徑", + "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此遊戲的存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此遊戲的系統設定資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此遊戲的 BCAT 資料夾\n", + "GameListContextMenuManageTitleUpdates": "管理遊戲更新", + "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟此遊戲的模組資料夾", + "GameListContextMenuCacheManagement": "快取管理", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", + "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的著色器快取", + "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此遊戲的 PPTC 快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此遊戲的著色器快取資料夾", + "GameListContextMenuExtractData": "提取資料", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", + "GameListContextMenuExtractDataLogo": "圖示", + "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", + "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", + "StatusBarSystemVersion": "系統版本: {0}", + "LinuxVmMaxMapCountDialogTitle": "檢測到映射的記憶體上限過低", + "LinuxVmMaxMapCountDialogTextPrimary": "你願意增加 vm.max_map_count to {0} 的數值嗎?", + "LinuxVmMaxMapCountDialogTextSecondary": "遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "碓定 (直至下一次重新啟動)", + "LinuxVmMaxMapCountDialogButtonPersistent": "碓定 (永遠設定)", + "LinuxVmMaxMapCountWarningTextPrimary": "映射記憶體的最大值少於目前建議的下限.", + "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值少於 {1}. 遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.\n\n你可能需要手動增加上限或安裝 pkexec, 這些都能協助Ryujinx完成此操作.", + "Settings": "設定", + "SettingsTabGeneral": "使用者介面", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", + "SettingsTabGeneralHideCursor": "隱藏滑鼠遊標:", + "SettingsTabGeneralHideCursorNever": "永不", + "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", + "SettingsTabGeneralHideCursorAlways": "總是", + "SettingsTabGeneralGameDirectories": "遊戲資料夾", + "SettingsTabGeneralAdd": "新增", + "SettingsTabGeneralRemove": "刪除", + "SettingsTabSystem": "系統", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美國", + "SettingsTabSystemSystemRegionEurope": "歐洲", + "SettingsTabSystemSystemRegionAustralia": "澳洲", + "SettingsTabSystemSystemRegionChina": "中國", + "SettingsTabSystemSystemRegionKorea": "韓國", + "SettingsTabSystemSystemRegionTaiwan": "台灣", + "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemLanguageJapanese": "日文", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", + "SettingsTabSystemSystemLanguageFrench": "法文", + "SettingsTabSystemSystemLanguageGerman": "德文", + "SettingsTabSystemSystemLanguageItalian": "義大利文", + "SettingsTabSystemSystemLanguageSpanish": "西班牙文", + "SettingsTabSystemSystemLanguageChinese": "中文 (中國)", + "SettingsTabSystemSystemLanguageKorean": "韓文", + "SettingsTabSystemSystemLanguageDutch": "荷蘭文", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙文", + "SettingsTabSystemSystemLanguageRussian": "俄文", + "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉丁美洲西班牙文", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemEnableVsync": "垂直同步", + "SettingsTabSystemEnablePptc": "啟用 PPTC 快取", + "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音效處理後台架構:", + "SettingsTabSystemAudioBackendDummy": "模擬", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", + "SettingsTabSystemExpandDramSize": "使用額外的記憶體佈局 (開發人員)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", + "SettingsTabGraphics": "圖像", + "SettingsTabGraphicsAPI": "圖像處理應用程式介面", + "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", + "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", + "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", + "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", + "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "螢幕長寬比例:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "伸展至螢幕大小", + "SettingsTabGraphicsDeveloperOptions": "開發者選項", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器轉存路徑:", + "SettingsTabLogging": "日誌", + "SettingsTabLoggingLogging": "日誌", + "SettingsTabLoggingEnableLoggingToFile": "儲存記錄日誌為檔案", + "SettingsTabLoggingEnableStubLogs": "啟用 Stub 記錄", + "SettingsTabLoggingEnableInfoLogs": "啟用資訊記錄", + "SettingsTabLoggingEnableWarningLogs": "啟用警告記錄", + "SettingsTabLoggingEnableErrorLogs": "啟用錯誤記錄", + "SettingsTabLoggingEnableTraceLogs": "啟用追蹤記錄", + "SettingsTabLoggingEnableGuestLogs": "啟用賓客記錄", + "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案存取記錄", + "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", + "SettingsTabLoggingDeveloperOptions": "開發者選項", + "SettingsTabLoggingDeveloperOptionsNote": "警告:此操作會降低效能", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖像處理後台記錄等級:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", + "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "啟用除錯記錄", + "SettingsTabInput": "輸入", + "SettingsTabInputEnableDockedMode": "Docked 模式", + "SettingsTabInputDirectKeyboardAccess": "鍵盤直接操作", + "SettingsButtonSave": "儲存", + "SettingsButtonClose": "關閉", + "SettingsButtonOk": "確定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "套用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌機模式", + "ControllerSettingsInputDevice": "輸入裝置", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "關閉", + "ControllerSettingsControllerType": "控制器類型", + "ControllerSettingsControllerTypeHandheld": "掌機", + "ControllerSettingsControllerTypeProController": "Nintendo Switch Pro控制器", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "配置檔案", + "ControllerSettingsProfileDefault": "預設", + "ControllerSettingsLoad": "載入", + "ControllerSettingsAdd": "建立", + "ControllerSettingsRemove": "刪除", + "ControllerSettingsButtons": "按鍵", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向鍵", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按鍵", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "搖桿", + "ControllerSettingsStickInvertXAxis": "搖桿左右反向", + "ControllerSettingsStickInvertYAxis": "搖桿上下反向", + "ControllerSettingsStickDeadzone": "盲區:", + "ControllerSettingsLStick": "左搖桿", + "ControllerSettingsRStick": "右搖桿", + "ControllerSettingsTriggersLeft": "左 Triggers", + "ControllerSettingsTriggersRight": "右 Triggers", + "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", + "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", + "ControllerSettingsTriggers": "板機", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左按鍵", + "ControllerSettingsExtraButtonsRight": "右按鍵", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "Triggers 閾值:", + "ControllerSettingsMotion": "傳感器", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 相容性傳感協定", + "ControllerSettingsMotionControllerSlot": "控制器插槽:", + "ControllerSettingsMotionMirrorInput": "鏡像輸入", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", + "ControllerSettingsMotionServerHost": "伺服器IP地址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀盲區:", + "ControllerSettingsSave": "儲存", + "ControllerSettingsClose": "關閉", + "UserProfilesSelectedUserProfile": "選擇使用者帳戶:", + "UserProfilesSaveProfileName": "儲存帳戶名稱", + "UserProfilesChangeProfileImage": "更換帳戶頭像", + "UserProfilesAvailableUserProfiles": "現有的使用者帳戶:", + "UserProfilesAddNewProfile": "建立帳戶", + "UserProfilesDelete": "刪除", + "UserProfilesClose": "關閉", + "ProfileNameSelectionWatermark": "選擇一個暱稱", + "ProfileImageSelectionTitle": "帳戶頭像選擇", + "ProfileImageSelectionHeader": "選擇帳戶頭像", + "ProfileImageSelectionNote": "你可以導入自訂頭像,或從系統中選擇頭像", + "ProfileImageSelectionImportImage": "導入圖片檔案", + "ProfileImageSelectionSelectAvatar": "選擇系統頭像", + "InputDialogTitle": "輸入對話框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "選擇帳戶名稱", + "InputDialogAddNewProfileHeader": "請輸入帳戶名稱", + "InputDialogAddNewProfileSubtext": "(最大長度:{0})", + "AvatarChoose": "選擇", + "AvatarSetBackgroundColor": "設定背景顏色", + "AvatarClose": "關閉", + "ControllerSettingsLoadProfileToolTip": "載入配置檔案", + "ControllerSettingsAddProfileToolTip": "新增配置檔案", + "ControllerSettingsRemoveProfileToolTip": "刪除配置檔案", + "ControllerSettingsSaveProfileToolTip": "儲存配置檔案", + "MenuBarFileToolsTakeScreenshot": "儲存截圖", + "MenuBarFileToolsHideUi": "隱藏使用者介面", + "GameListContextMenuRunApplication": "執行程式", + "GameListContextMenuToggleFavorite": "標記為收藏", + "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", + "SettingsTabGeneralTheme": "佈景主題", + "SettingsTabGeneralThemeCustomTheme": "自訂佈景主題路徑", + "SettingsTabGeneralThemeBaseStyle": "基本佈景主題式樣", + "SettingsTabGeneralThemeBaseStyleDark": "深色模式", + "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂佈景主題", + "ButtonBrowse": "瀏覽", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震動", + "ControllerSettingsRumbleStrongMultiplier": "強震動調節", + "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", + "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", + "DialogConfirmationTitle": "Ryujinx - 設定", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 錯誤", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 關閉", + "DialogErrorMessage": "Ryujinx 遇到了錯誤", + "DialogExitMessage": "你確定要關閉 Ryujinx 嗎?", + "DialogExitSubMessage": "所有未儲存的資料將會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出現錯誤: {0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出現錯誤: {0}", + "FolderDialogExtractTitle": "選擇要解壓到的資料夾", + "DialogNcaExtractionMessage": "提取{1}的{0}分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "你使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時失敗。可能是因為 GitHub Actions 正在編譯新版本。請於數分數後重試。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下載最新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", + "DialogUpdaterAddingFilesMessage": "安裝更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "你確定要立即重新啟動 Ryujinx 嗎?", + "DialogUpdaterArchNotSupportedMessage": "你執行的系統架構不被支援!", + "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", + "DialogUpdaterNoInternetMessage": "你沒有連接到網際網絡!", + "DialogUpdaterNoInternetSubMessage": "請確保網際網絡連接正常!", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果你希望使用被受支援的Ryujinx版本,請你在官方網址 https://ryujinx.org/ 下載.", + "DialogRestartRequiredMessage": "模擬器必須重新啟動", + "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能生效。", + "DialogThemeRestartSubMessage": "你確定要現在重新啟動嗎?", + "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", + "DialogFirmwareInstalledMessage": "已安裝韌體{0}", + "DialogInstallFileTypesSuccessMessage": "成功註冊檔案類型!", + "DialogInstallFileTypesErrorMessage": "註冊檔案類型失敗。", + "DialogUninstallFileTypesSuccessMessage": "成功取消註冊檔案類型!", + "DialogUninstallFileTypesErrorMessage": "取消註冊檔案類型失敗。", + "DialogOpenSettingsWindowLabel": "開啟設定視窗", + "DialogControllerAppletTitle": "控制器小視窗", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出現錯誤: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出現錯誤: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", + "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", + "DialogAmiiboApiTitle": "Amiibo 應用程式介面", + "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", + "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或你沒有連接到網際網路。", + "DialogProfileInvalidProfileErrorMessage": "配置檔案 {0} 與目前輸入系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", + "DialogProfileDeleteProfileTitle": "刪除帳戶", + "DialogProfileDeleteProfileMessage": "此操作不可撤銷, 您確定要繼續嗎?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "下一次重啟時將會重新建立以下遊戲的 PPTC 快取\n\n{0}\n\n你確定要繼續嗎?", + "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "即將刪除以下遊戲的著色器快取:\n\n{0}\n\n你確定要繼續嗎?", + "DialogShaderDeletionErrorMessage": "清除{0}的著色器快取時出現錯誤: {1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "你確定要繼續嗎?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", + "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳戶", + "DialogUserProfileDeletionConfirmMessage": "你確定要刪除選擇中的帳戶嗎?", + "DialogUserProfileUnsavedChangesTitle": "警告 - 有未儲存的更改", + "DialogUserProfileUnsavedChangesMessage": "你對此帳戶所做的更改尚未儲存.", + "DialogUserProfileUnsavedChangesSubMessage": "你確定要捨棄更改嗎?", + "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入配置檔案已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "你確定要儲存嗎?", + "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", + "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "你啟用了跟蹤記錄,它的設計僅限開發人員使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用追蹤記錄。你是否要立即停用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "你啟用了著色器轉存,它的設計僅限開發人員使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用著色器轉存。你是否要立即停用?", + "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入此遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", + "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後台多工執行中", + "DialogSettingsBackendThreadingWarningMessage": "更改此選項後必須重啟 Ryujinx 才能生效。根據你的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GPU多執行緒。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "圖像處理後台多線程支援:", + "CommonAuto": "自動(推薦)", + "CommonOff": "關閉", + "CommonOn": "打開", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", + "MenuBarOptionsPauseEmulation": "暫停", + "MenuBarOptionsResumeEmulation": "繼續", + "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官方網站。", + "AboutDisclaimerMessage": "Ryujinx 與 Nintendo™ 並沒有任何關聯, 包括其合作伙伴, 及任何形式上。", + "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助網頁。", + "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", + "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", + "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是 Nintendo Switch™ 的一款模擬器。\n懇請您在 Patreon 上贊助我們。\n關注 Twitter 或 Discord 可以取得我們的最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", + "AboutRyujinxMaintainersTitle": "開發及維護名單:", + "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "Patreon 的贊助人:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "掃描", + "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "侵略:使用隨機標記的 Uuid 編碼", + "DlcManagerTableHeadingEnabledLabel": "已啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", + "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", + "DlcManagerTableHeadingFullPathLabel": "完整路徑", + "DlcManagerRemoveAllButton": "全部刪除", + "DlcManagerEnableAllButton": "啟用全部", + "DlcManagerDisableAllButton": "停用全部", + "MenuBarOptionsChangeLanguage": "更改語言", + "MenuBarShowFileTypes": "顯示檔案類型", + "CommonSort": "排序", + "CommonShowNames": "顯示名稱", + "CommonFavorite": "收藏", + "OrderAscending": "從小到大", + "OrderDescending": "從大到小", + "SettingsTabGraphicsFeatures": "功能及優化", + "ErrorWindowTitle": "錯誤視窗", + "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", + "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", + "AddGameDirTooltip": "添加遊戲資料夾到列表中", + "RemoveGameDirTooltip": "移除選擇中的遊戲資料夾", + "CustomThemeCheckTooltip": "啟用或關閉自訂佈景主題", + "CustomThemePathTooltip": "自訂佈景主題的資料夾", + "CustomThemeBrowseTooltip": "查找自訂佈景主題", + "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", + "DirectKeyboardTooltip": "支援鍵盤直接存取 (HID協定) . 可供給遊戲使用你的鍵盤作為輸入文字裝置.", + "DirectMouseTooltip": "支援滑鼠直接存取 (HID協定) . 可供給遊戲使用你的滑鼠作為瞄準裝置.", + "RegionTooltip": "更改系統區域", + "LanguageTooltip": "更改系統語言", + "TimezoneTooltip": "更改系統時區", + "TimeTooltip": "更改系統時鐘", + "VSyncToggleTooltip": "模擬遊戲主機垂直同步更新頻率. 重要地反映著遊戲本身的速度; 關閉它可能會令後使用動態更新率的遊戲速度過高, 或會引致載入錯誤等等.\n\n可在遊戲中利用自訂快速鍵開關此功能. 我們也建議使用快速鍵, 如果你計劃關上它.\n\n如果不確定請設定為\"開啟\".", + "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", + "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", + "AudioBackendTooltip": "更改音效處理後台架構.\n\n推薦使用SDL2架構, 而OpenAL及SoundIO架構用作後備. Dummy是沒有音效的.\n\n如果不確定請設定為\"SDL2\".", + "MemoryManagerTooltip": "更改模擬器記憶體至電腦記憶體的映射和存取方式,極其影響CPU效能.\n\n如果不確定, 請設定為\"主機略過檢查模式\".", + "MemoryManagerSoftwareTooltip": "使用軟體虛擬分頁表換算, 最精確但是速度最慢.", + "MemoryManagerHostTooltip": "直接地映射模擬記憶體到電腦記憶體. 對 JIT 編譯和執行效率有顯著提升. ", + "MemoryManagerUnsafeTooltip": "直接地映射模擬記憶體到電腦記憶體, 但是不檢查記憶體溢出. 由於 Ryujinx 的子程式可以存取任何位置的記憶體, 因而相對不安全. 故在此模式下只應執行你信任的遊戲或軟體.", + "UseHypervisorTooltip": "使用 Hypervisor 代替 JIT。在本功能可用時就可以大幅增大效能,但目前狀態還不穩定。", + "DRamTooltip": "利用可選擇性的記憶體模式來模擬Switch發展中型號.\n\n此選項只會對高畫質材質包或4K模組有用. 而這並不會提升效能. \n\n如果不確定請關閉本功能.", + "IgnoreMissingServicesTooltip": "忽略某些未被實施的系統服務. 此功能有助於繞過當啟動遊戲時帶來的故障.\n\n如果不確定請關閉本功能。", + "GraphicsBackendThreadingTooltip": "執行雙線程後台繪圖指令, 能夠減少著色器編譯斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", + "GalThreadingTooltip": "執行雙線程後台繪圖指令.\n\n能夠加速著色器編譯及減少斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", + "ShaderCacheToggleTooltip": "儲存著色器快取到硬碟,減少存取斷續。\n\n如果不確定請設定為\"開啟\"。", + "ResolutionScaleTooltip": "解析度繪圖倍率", + "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", + "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", + "AspectRatioTooltip": "模擬器視窗解析度的長寬比", + "ShaderDumpPathTooltip": "圖形著色器轉存路徑", + "FileLogTooltip": "是否儲存日誌檔案到硬碟", + "StubLogTooltip": "在控制台顯示及記錄 Stub 訊息", + "InfoLogTooltip": "在控制台顯示及記錄資訊訊息", + "WarnLogTooltip": "在控制台顯示及記錄警告訊息\n", + "ErrorLogTooltip": "在控制台顯示及記錄錯誤訊息", + "TraceLogTooltip": "在控制台顯示及記錄追蹤訊息", + "GuestLogTooltip": "在控制台顯示及記錄賓客訊息", + "FileAccessLogTooltip": "在控制台顯示及記錄檔案存取訊息", + "FSAccessLogModeTooltip": "在控制台顯示及記錄FS 存取訊息. 可選的模式是 0-3", + "DeveloperOptionTooltip": "使用請謹慎", + "OpenGlLogLevel": "需要打開適當的日誌等級", + "DebugLogTooltip": "在控制台顯示及記錄除錯訊息.\n\n僅限於受訓的成員使用, 因為它很難理解而且令模擬的效能非常地差.\n", + "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", + "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", + "OpenRyujinxFolderTooltip": "開啟 Ryujinx 系統資料夾", + "OpenRyujinxLogsTooltip": "開啟存放日誌的資料夾", + "ExitTooltip": "關閉 Ryujinx", + "OpenSettingsTooltip": "開啟設定視窗", + "OpenProfileManagerTooltip": "開啟使用者帳戶管理視窗", + "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", + "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", + "OpenAboutTooltip": "開啟關於視窗", + "GridSize": "網格尺寸", + "GridSizeTooltip": "調整網格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", + "AboutRyujinxContributorsButtonHeader": "查看所有參與者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "調節音量", + "SettingsTabSystemEnableInternetAccess": "啟用網路連接", + "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "範圍:", + "DialogStopEmulationTitle": "Ryujinx - 停止模擬", + "DialogStopEmulationMessage": "你確定要停止模擬嗎?", + "SettingsTabCpu": "處理器", + "SettingsTabAudio": "音訊", + "SettingsTabNetwork": "網路", + "SettingsTabNetworkConnection": "網路連接", + "SettingsTabCpuCache": "CPU 快取", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已停用!", + "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此遊戲額外的SD記憶卡Atmosphere模組資料夾. 有用於包裝在真實主機上的模組.\n", + "ControllerSettingsRotate90": "順時針旋轉 90°", + "IconSize": "圖示尺寸", + "IconSizeTooltip": "更改遊戲圖示大小", + "MenuBarOptionsShowConsole": "顯示控制台", + "ShaderCachePurgeError": "清除 {0} 著色器快取時出現錯誤: {1}", + "UserErrorNoKeys": "找不到金鑰", + "UserErrorNoFirmware": "找不到韌體", + "UserErrorFirmwareParsingFailed": "韌體解析錯誤", + "UserErrorApplicationNotFound": "找不到應用程式", + "UserErrorUnknown": "未知錯誤", + "UserErrorUndefined": "未定義錯誤", + "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", + "OpenSetupGuideMessage": "開啟設定教學", + "NoUpdate": "沒有新版本", + "TitleUpdateVersionLabel": "版本 {0} - {1}", + "RyujinxInfo": "Ryujinx - 訊息", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "全部類型", + "Never": "從不", + "SwkbdMinCharacters": "至少應為 {0} 個字長", + "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", + "SoftwareKeyboard": "軟體鍵盤", + "SoftwareKeyboardModeNumbersOnly": "只接受數字", + "SoftwareKeyboardModeAlphabet": "不支援中日韓統一表意文字字元", + "SoftwareKeyboardModeASCII": "只接受 ASCII 符號", + "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", + "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", + "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", + "UpdaterRenaming": "正在重新命名舊檔案...", + "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", + "UpdaterAddingFiles": "安裝更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下載新版本中...", + "Game": "遊戲", + "Docked": "主機模式", + "Handheld": "掌機模式", + "ConnectionError": "連接錯誤。", + "AboutPageDeveloperListMore": "{0} 等開發者...", + "ApiError": "API 錯誤", + "LoadingHeading": "正在啟動 {0}", + "CompilingPPTC": "編譯 PPTC 快取中", + "CompilingShaders": "編譯著色器中", + "AllKeyboards": "所有鍵盤", + "OpenFileDialogTitle": "選擇支援的檔案格式", + "OpenFolderDialogTitle": "選擇一個包含已解開封裝遊戲的資料夾\n", + "AllSupportedFormats": "全部支援的格式", + "RyujinxUpdater": "Ryujinx 更新程式", + "SettingsTabHotkeys": "快捷鍵", + "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "截圖:", + "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面:", + "SettingsTabHotkeysPauseHotkey": "暫停:", + "SettingsTabHotkeysToggleMuteHotkey": "靜音:", + "ControllerMotionTitle": "體感操作設定", + "ControllerRumbleTitle": "震動設定", + "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", + "SettingsXamlThemeFile": "Xaml 主題檔案", + "AvatarWindowTitle": "管理帳號 - 頭貼", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "用途", + "Writable": "可寫入", + "SelectDlcDialogTitle": "選擇 DLC 檔案", + "SelectUpdateDialogTitle": "選擇更新檔", + "UserProfileWindowTitle": "管理使用者帳戶", + "CheatWindowTitle": "管理遊戲金手指", + "DlcWindowTitle": "管理遊戲 DLC", + "UpdateWindowTitle": "管理遊戲更新", + "CheatWindowHeading": "金手指可用於 {0} [{1}]", + "BuildId": "版本編號:", + "DlcWindowHeading": "DLC 可用於 {0} [{1}]", + "UserProfilesEditProfile": "編輯所選", + "Cancel": "取消", + "Save": "儲存", + "Discard": "放棄更改", + "UserProfilesSetProfileImage": "設定帳戶頭像", + "UserProfileEmptyNameError": "使用者名稱為必填", + "UserProfileNoImageError": "必須設定帳戶頭像", + "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "使用者名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖像處理後台架構", + "SettingsTabGraphicsBackendTooltip": "用來圖像處理的後台架構", + "SettingsEnableTextureRecompression": "開啟材質重新壓縮", + "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", + "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇支持運行Vulkan圖像處理架構的GPU.\n\n此設定不會影響GPU運行OpenGL.\n\n如果不確定, 請設定標籤為\"dGPU\"的GPU. 如果沒有, 請保留原始設定.", + "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖像處理後台架構或GPU相關設定已被修改。需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "你確定要現在重新啟動嗎?", + "RyujinxUpdaterMessage": "你確定要將 Ryujinx 更新到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableMacroHLE": "啟用 Macro HLE", + "SettingsEnableMacroHLETooltip": "GPU 微代碼的高階模擬。\n\n可以提升效能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請設定為\"開啟\"。", + "SettingsEnableColorSpacePassthrough": "色彩空間直通", + "SettingsEnableColorSpacePassthroughTooltip": "指揮Vulkan後端直通色彩資訊而不需指定色彩空間,對於廣色域顯示的使用者,這會造成較多抖色,犧牲色彩正確性。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理遊戲存檔", + "DeleteUserSave": "你確定要刪除此遊戲的存檔嗎?", + "IrreversibleActionNote": "本動作將無法挽回。", + "SaveManagerHeading": "管理 {0} 的遊戲存檔", + "SaveManagerTitle": "遊戲存檔管理器", + "Name": "名稱", + "Size": "大小", + "Search": "搜尋", + "UserProfilesRecoverLostAccounts": "恢復遺失的帳戶", + "Recover": "恢復", + "UserProfilesRecoverHeading": "在以下帳戶找到了一些遊戲存檔", + "UserProfilesRecoverEmptyList": "沒有可恢復的使用者帳戶", + "GraphicsAATooltip": "在遊戲繪圖上套用抗鋸齒", + "GraphicsAALabel": "抗鋸齒:", + "GraphicsScalingFilterLabel": "縮放過濾器:", + "GraphicsScalingFilterTooltip": "啟用畫幀緩衝區縮放", + "GraphicsScalingFilterLevelLabel": "記錄檔等級", + "GraphicsScalingFilterLevelTooltip": "設定縮放過濾器的強度", + "SmaaLow": "低階 SMAA", + "SmaaMedium": "中階 SMAA", + "SmaaHigh": "高階 SMAA", + "SmaaUltra": "超高階 SMAA", + "UserEditorTitle": "編輯使用者", + "UserEditorTitleCreate": "建立使用者", + "SettingsTabNetworkInterface": "網路介面:", + "NetworkInterfaceTooltip": "用於具有 LAN 功能的網路介面", + "NetworkInterfaceDefault": "預設", + "PackagingShaders": "著色器封裝", + "AboutChangelogButton": "在 GitHub 查看更新日誌", + "AboutChangelogButtonTooltipMessage": "在瀏覽器中打開此Ryujinx版本的更新日誌。" +} \ No newline at end of file diff --git a/src/Ryujinx/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml new file mode 100644 index 00000000..f7f64be2 --- /dev/null +++ b/src/Ryujinx/Assets/Styles/Styles.xaml @@ -0,0 +1,396 @@ +<Styles + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> + <Design.PreviewWith> + <Border Height="2000" + Padding="20"> + <StackPanel Spacing="5"> + <TextBlock Text="Code Font Family" /> + <Grid RowDefinitions="*,Auto"> + <Menu Grid.Row="1" + Width="100"> + <MenuItem Header="File"> + <MenuItem Header="Test 1" /> + <MenuItem Header="Test 2" /> + <MenuItem Header="Test 3"> + <MenuItem.Icon> + <CheckBox Margin="0" + IsChecked="{ReflectionBinding Checkbox, Mode=TwoWay}" /> + </MenuItem.Icon> + </MenuItem> + </MenuItem> + </Menu> + <StackPanel Orientation="Horizontal"> + <ToggleButton + Name="btnAdd" + Height="28" + HorizontalAlignment="Right" + Content="Addy" /> + <Button + Name="btnRem" + HorizontalAlignment="Right" + Content="Add" /> + <TextBox + Width="100" + VerticalAlignment="Center" + Text="Rrrrr" + UseFloatingWatermark="True" + Watermark="Hello" /> + <CheckBox>Test Check</CheckBox> + </StackPanel> + </Grid> + <ui:NumberBox Value="1" /> + </StackPanel> + </Border> + </Design.PreviewWith> + <Style Selector="Border.small"> + <Setter Property="Width" + Value="100" /> + </Style> + <Style Selector="Border.normal"> + <Setter Property="Width" + Value="130" /> + </Style> + <Style Selector="Border.large"> + <Setter Property="Width" + Value="160" /> + </Style> + <Style Selector="Border.huge"> + <Setter Property="Width" + Value="200" /> + </Style> + <Style Selector="Border.settings"> + <Setter Property="Background" + Value="{DynamicResource ThemeDarkColor}" /> + <Setter Property="BorderBrush" + Value="{DynamicResource MenuFlyoutPresenterBorderColor}" /> + <Setter Property="BorderThickness" + Value="1" /> + <Setter Property="CornerRadius" + Value="5" /> + </Style> + <Style Selector="Image.small"> + <Setter Property="Width" + Value="50" /> + </Style> + <Style Selector="Image.normal"> + <Setter Property="Width" + Value="80" /> + </Style> + <Style Selector="Image.large"> + <Setter Property="Width" + Value="100" /> + </Style> + <Style Selector="Image.huge"> + <Setter Property="Width" + Value="120" /> + </Style> + <Style Selector="#TitleBarHost > Image"> + <Setter Property="Margin" + Value="10" /> + </Style> + <Style Selector="#TitleBarHost > Label"> + <Setter Property="Margin" + Value="5" /> + <Setter Property="FontSize" + Value="14" /> + </Style> + <Style Selector="Button.SystemCaption"> + <Setter Property="MinWidth" + Value="10" /> + </Style> + <Style Selector="DataGridColumnHeader"> + <Setter Property="Foreground" + Value="{DynamicResource ThemeForegroundBrush}" /> + <Setter Property="HorizontalContentAlignment" + Value="Center" /> + <Setter Property="BorderThickness" + Value="1" /> + <Setter Property="VerticalContentAlignment" + Value="Center" /> + <Setter Property="SeparatorBrush" + Value="{DynamicResource ThemeControlBorderColor}" /> + <Setter Property="Padding" + Value="5" /> + <Setter Property="Background" + Value="{DynamicResource ThemeContentBackgroundColor}" /> + <Setter Property="Template"> + <ControlTemplate> + <Grid Background="{TemplateBinding Background}" + ColumnDefinitions="*,Auto"> + <Grid + Margin="{TemplateBinding Padding}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + ColumnDefinitions="*,Auto"> + <ContentPresenter Content="{TemplateBinding Content}" /> + <Path + Name="SortIcon" + Grid.Column="1" + Width="8" + Margin="4,0,0,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z " + Fill="{TemplateBinding Foreground}" + Stretch="Uniform" /> + </Grid> + <Rectangle + Name="VerticalSeparator" + Grid.Column="1" + Width="1" + VerticalAlignment="Stretch" + Fill="{TemplateBinding SeparatorBrush}" + IsVisible="{TemplateBinding AreSeparatorsVisible}" /> + </Grid> + </ControlTemplate> + </Setter> + </Style> + <Style Selector="DataGrid"> + <Setter Property="RowBackground" + Value="{DynamicResource ThemeAccentBrush4}" /> + <Setter Property="Background" + Value="{DynamicResource ThemeBackgroundBrush}" /> + <Setter Property="BorderBrush" + Value="{DynamicResource ThemeBorderLowColor}" /> + <Setter Property="BorderThickness" + Value="{DynamicResource ThemeBorderThickness}" /> + </Style> + <Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle"> + <Setter Property="Fill" + Value="{DynamicResource SystemAccentColor}" /> + <Setter Property="Opacity" + Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" /> + </Style> + <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle"> + <Setter Property="Fill" + Value="{DynamicResource SystemListLowColor}" /> + </Style> + <Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle"> + <Setter Property="Fill" + Value="{DynamicResource SystemAccentColor}" /> + <Setter Property="Opacity" + Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" /> + </Style> + <Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle"> + <Setter Property="Fill" + Value="{DynamicResource SystemAccentColor}" /> + <Setter Property="Opacity" + Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" /> + </Style> + <Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle"> + <Setter Property="Fill" + Value="{DynamicResource SystemAccentColor}" /> + <Setter Property="Opacity" + Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" /> + </Style> + <Style Selector="DataGridCell"> + <Setter Property="HorizontalAlignment" + Value="Center" /> + <Setter Property="HorizontalContentAlignment" + Value="Center" /> + </Style> + <Style Selector="DataGridCell.Left"> + <Setter Property="HorizontalAlignment" + Value="Left" /> + </Style> + <Style Selector="CheckBox"> + <Setter Property="BorderThickness" + Value="1" /> + </Style> + + <Style Selector="MenuItem"> + <Setter Property="Height" + Value="{DynamicResource MenuItemHeight}" /> + <Setter Property="Padding" + Value="{DynamicResource MenuItemPadding}" /> + <Setter Property="FontSize" + Value="12" /> + </Style> + <Style Selector="MenuItem:selected /template/ Border#root"> + <Setter Property="Background" + Value="{DynamicResource ThemeControlBorderColor}" /> + <Setter Property="BorderBrush" + Value="{DynamicResource ThemeControlBorderColor}" /> + </Style> + <Style Selector="TabItem > ScrollViewer"> + <Setter Property="Background" + Value="{DynamicResource ThemeBackgroundColor}" /> + <Setter Property="Margin" + Value="0,-5,0,0" /> + </Style> + <Style Selector="TabItem > ScrollViewer > Border"> + <Setter Property="BorderThickness" + Value="0,1,0,0" /> + <Setter Property="Background" + Value="{DynamicResource ThemeBackgroundColor}" /> + <Setter Property="BorderBrush" + Value="{DynamicResource HighlightBrush}" /> + </Style> + <Style Selector="Button"> + <Setter Property="MinWidth" + Value="80" /> + </Style> + <Style Selector="ProgressBar /template/ Border#ProgressBarTrack"> + <Setter Property="IsVisible" + Value="False" /> + </Style> + <Style Selector="ToggleButton"> + <Setter Property="Padding" + Value="0,-5,0,0" /> + </Style> + <Style Selector="TabItem"> + <Setter Property="FontSize" + Value="14" /> + <Setter Property="BorderThickness" + Value="0,0,1,0" /> + <Setter Property="BorderBrush" + Value="{DynamicResource ThemeButtonForegroundColor}" /> + <Setter Property="Background" + Value="{DynamicResource SystemAccentColorLight2}" /> + </Style> + <Style Selector="TabItem:pointerover"> + <Setter Property="Foreground" + Value="{DynamicResource ThemeButtonForegroundColor}" /> + </Style> + <Style Selector="TabItem:selected"> + <Setter Property="Background" + Value="{DynamicResource SystemAccentColorLight2}" /> + <Setter Property="Foreground" + Value="{DynamicResource ThemeBackgroundColor}" /> + </Style> + <Style Selector="TextBlock"> + <Setter Property="Margin" + Value="{DynamicResource TextMargin}" /> + <Setter Property="FontSize" + Value="{DynamicResource FontSize}" /> + <Setter Property="VerticalAlignment" + Value="Center" /> + <Setter Property="TextWrapping" + Value="WrapWithOverflow" /> + </Style> + <Style Selector="TextBlock.h1"> + <Setter Property="Margin" + Value="{DynamicResource TextMargin}" /> + <Setter Property="VerticalAlignment" + Value="Center" /> + <Setter Property="FontWeight" + Value="Bold" /> + <Setter Property="FontSize" + Value="16" /> + <Setter Property="TextWrapping" + Value="WrapWithOverflow" /> + </Style> + <Style Selector="Separator"> + <Setter Property="Background" + Value="{DynamicResource ThemeControlBorderColor}" /> + <Setter Property="Foreground" + Value="{DynamicResource ThemeControlBorderColor}" /> + <Setter Property="MinHeight" + Value="1" /> + </Style> + <Style Selector=":is(Button).DateTimeFlyoutButtonStyle"> + <Setter Property="Background" + Value="{DynamicResource SystemAccentColorLight2}" /> + <Setter Property="Foreground" + Value="{DynamicResource ThemeBackgroundColor}" /> + </Style> + <Style Selector="DatePickerPresenter"> + <Setter Property="BorderThickness" + Value="1" /> + <Setter Property="BorderBrush" + Value="{DynamicResource ThemeButtonForegroundColor}" /> + </Style> + <Style Selector="DataGridCell"> + <Setter Property="FontSize" + Value="14" /> + </Style> + <Style Selector="CheckBox TextBlock"> + <Setter Property="Margin" + Value="0,5,0,0" /> + </Style> + <Style Selector="ContextMenu"> + <Setter Property="BorderBrush" + Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" /> + <Setter Property="BorderThickness" + Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" /> + </Style> + <Style Selector="TextBox"> + <Setter Property="VerticalContentAlignment" + Value="Center" /> + </Style> + <Style Selector="TextBox.NumberBoxTextBoxStyle"> + <Setter Property="Foreground" + Value="{DynamicResource ThemeForegroundColor}" /> + </Style> + <Style Selector="ListBox ListBoxItem"> + <Setter Property="Padding" + Value="0" /> + <Setter Property="Margin" + Value="0" /> + <Setter Property="CornerRadius" + Value="5" /> + <Setter Property="Background" + Value="{DynamicResource AppListBackgroundColor}" /> + <Setter Property="BorderThickness" + Value="2"/> + </Style> + <Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter"> + <Setter Property="Background" + Value="{DynamicResource AppListBackgroundColor}" /> + </Style> + <Style Selector="ListBox"> + <Setter Property="Background" + Value="{DynamicResource ThemeContentBackgroundColor}" /> + </Style> + <Style Selector="FlyoutPresenter, ContextMenu, MenuFlyoutPresenter"> + <Setter Property="BorderBrush" + Value="{DynamicResource MenuFlyoutPresenterBorderColor}" /> + </Style> + <Style Selector="ListBox ListBoxItem:pointerover /template/ ContentPresenter"> + <Setter Property="Background" + Value="{DynamicResource AppListHoverBackgroundColor}" /> + </Style> + <Styles.Resources> + <SolidColorBrush x:Key="ThemeAccentColorBrush" + Color="{DynamicResource SystemAccentColor}" /> + <StaticResource x:Key="ListViewItemBackgroundSelected" + ResourceKey="ThemeAccentColorBrush" /> + <StaticResource x:Key="ListViewItemBackgroundPressed" + ResourceKey="SystemAccentColorDark1" /> + <StaticResource x:Key="ListViewItemBackgroundPointerOver" + ResourceKey="SystemAccentColorDark2" /> + <StaticResource x:Key="ListViewItemBackgroundSelectedPressed" + ResourceKey="ThemeAccentColorBrush" /> + <StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" + ResourceKey="SystemAccentColorDark2" /> + <SolidColorBrush + x:Key="DataGridGridLinesBrush" + Opacity="0.4" + Color="{DynamicResource SystemBaseMediumLowColor}" /> + <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" + Color="{DynamicResource DataGridSelectionColor}" /> + <SolidColorBrush x:Key="SplitButtonBackgroundChecked" + Color="#00E81123" /> + <SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver" + Color="#00E81123" /> + <SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed" + Color="#00E81123" /> + <SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" + Color="#00E81123" /> + <Thickness x:Key="PageMargin">40 0 40 0</Thickness> + <Thickness x:Key="Margin">0 5 0 5</Thickness> + <Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness> + <x:Double x:Key="ScrollBarThickness">15</x:Double> + <x:Double x:Key="FontSizeSmall">8</x:Double> + <x:Double x:Key="FontSizeNormal">10</x:Double> + <x:Double x:Key="FontSize">12</x:Double> + <x:Double x:Key="FontSizeLarge">15</x:Double> + <x:Double x:Key="ControlContentThemeFontSize">13</x:Double> + <x:Double x:Key="MenuItemHeight">26</x:Double> + <x:Double x:Key="TabItemMinHeight">28</x:Double> + <x:Double x:Key="ContentDialogMaxWidth">600</x:Double> + <x:Double x:Key="ContentDialogMaxHeight">756</x:Double> + </Styles.Resources> +</Styles> \ No newline at end of file diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml new file mode 100644 index 00000000..0f323f84 --- /dev/null +++ b/src/Ryujinx/Assets/Styles/Themes.xaml @@ -0,0 +1,85 @@ +<ResourceDictionary xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <ResourceDictionary.ThemeDictionaries> + <ResourceDictionary x:Key="Default"> + <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" + Color="{DynamicResource DataGridSelectionColor}" /> + <SolidColorBrush x:Key="ThemeAccentColorBrush" + Color="{DynamicResource SystemAccentColor}" /> + <SolidColorBrush x:Key="ThemeAccentBrush4" + Color="{DynamicResource ThemeAccentColor4}" /> + <Color x:Key="SystemAccentColor">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color> + <Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color> + <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> + <Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color> + <Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color> + <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> + <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> + <Color x:Key="ThemeForegroundColor">#FF000000</Color> + <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> + <Color x:Key="AppListBackgroundColor">#b3ffffff</Color> + <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> + <Color x:Key="SecondaryTextColor">#A0000000</Color> + <Color x:Key="VsyncEnabled">#FF2EEAC9</Color> + <Color x:Key="VsyncDisabled">#FFFF4554</Color> + </ResourceDictionary> + <ResourceDictionary x:Key="Light"> + <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" + Color="{DynamicResource DataGridSelectionColor}" /> + <SolidColorBrush x:Key="ThemeAccentColorBrush" + Color="{DynamicResource SystemAccentColor}" /> + <SolidColorBrush x:Key="ThemeAccentBrush4" + Color="{DynamicResource ThemeAccentColor4}" /> + <Color x:Key="SystemAccentColor">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color> + <Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color> + <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> + <Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color> + <Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color> + <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> + <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> + <Color x:Key="ThemeForegroundColor">#FF000000</Color> + <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> + <Color x:Key="AppListBackgroundColor">#b3ffffff</Color> + <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> + <Color x:Key="SecondaryTextColor">#A0000000</Color> + </ResourceDictionary> + <ResourceDictionary x:Key="Dark"> + <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" + Color="{DynamicResource DataGridSelectionColor}" /> + <SolidColorBrush x:Key="ThemeAccentColorBrush" + Color="{DynamicResource SystemAccentColor}" /> + <SolidColorBrush x:Key="ThemeAccentBrush4" + Color="{DynamicResource ThemeAccentColor4}" /> + <Color x:Key="ControlFillColorSecondary">#008AA8</Color> + <Color x:Key="SystemAccentColor">#FF00C3E3</Color> + <Color x:Key="SystemAccentColorDark1">#FF99b000</Color> + <Color x:Key="SystemAccentColorDark2">#FF006d7d</Color> + <Color x:Key="SystemAccentColorDark3">#FF00525E</Color> + <Color x:Key="SystemAccentColorLight1">#FF00dbff</Color> + <Color x:Key="SystemAccentColorLight2">#FF19dfff</Color> + <Color x:Key="SystemAccentColorLight3">#FF33e3ff</Color> + <Color x:Key="DataGridSelectionColor">#FF00FABB</Color> + <Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color> + <Color x:Key="ThemeControlBorderColor">#FF505050</Color> + <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> + <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> + <Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color> + <Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color> + <Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color> + <Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color> + <Color x:Key="SecondaryTextColor">#A0FFFFFF</Color> + </ResourceDictionary> + </ResourceDictionary.ThemeDictionaries> +</ResourceDictionary> diff --git a/src/Ryujinx/Common/ApplicationHelper.cs b/src/Ryujinx/Common/ApplicationHelper.cs new file mode 100644 index 00000000..622a6a02 --- /dev/null +++ b/src/Ryujinx/Common/ApplicationHelper.cs @@ -0,0 +1,419 @@ +using Avalonia.Controls.Notifications; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +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 System; +using System.Buffers; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ApplicationId = LibHac.Ncm.ApplicationId; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.Common +{ + internal static class ApplicationHelper + { + private static HorizonClient _horizonClient; + private static AccountManager _accountManager; + private static VirtualFileSystem _virtualFileSystem; + + public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient) + { + _virtualFileSystem = virtualFileSystem; + _horizonClient = horizonClient; + _accountManager = accountManager; + } + + private static bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId) + { + saveDataId = default; + + Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); + if (ResultFs.TargetNotFound.Includes(result)) + { + ref ApplicationControlProperty control = ref controlHolder.Value; + + Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]"); + + if (controlHolder.ByteSpan.IsZeros()) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct<ApplicationControlProperty>(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); + + result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new ApplicationId(titleId), in control, in user); + if (result.IsFailure()) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName())); + }); + + return false; + } + + // Try to find the savedata again after creating it + result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); + } + + if (result.IsSuccess()) + { + saveDataId = saveDataInfo.SaveDataId; + + return true; + } + + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageFindSaveErrorMessage, result.ToStringWithName())); + }); + + return false; + } + + public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, BlitStruct<ApplicationControlProperty> controlData, string titleName) + { + if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId)) + { + return; + } + + OpenSaveDir(saveDataId); + } + + public static void OpenSaveDir(ulong saveDataId) + { + string saveRootPath = Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); + + if (!Directory.Exists(saveRootPath)) + { + // Inconsistent state. Create the directory + Directory.CreateDirectory(saveRootPath); + } + + string committedPath = Path.Combine(saveRootPath, "0"); + string workingPath = Path.Combine(saveRootPath, "1"); + + // If the committed directory exists, that path will be loaded the next time the savedata is mounted + if (Directory.Exists(committedPath)) + { + OpenHelper.OpenFolder(committedPath); + } + else + { + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } + + OpenHelper.OpenFolder(workingPath); + } + } + + public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0) + { + var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], + AllowMultiple = false, + }); + + if (result.Count == 0) + { + return; + } + + var destination = result[0].Path.LocalPath; + var cancellationToken = new CancellationTokenSource(); + + UpdateWaitWindow waitingDialog = new( + LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)), + cancellationToken); + + Thread extractorThread = new(() => + { + Dispatcher.UIThread.Post(waitingDialog.Show); + + using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); + + Nca mainNca = null; + Nca patchNca = null; + + string extension = Path.GetExtension(titleFilePath).ToLower(); + if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") + { + IFileSystem pfs; + + if (extension == ".xci") + { + pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).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<IFile>(); + + 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) + { + 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 (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"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]); + }); + + return; + } + + (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType); + + try + { + bool sectionExistsInPatch = false; + if (patchNca != null) + { + sectionExistsInPatch = patchNca.CanOpenSection(index); + } + + IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) + : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); + + FileSystemClient fsClient = _horizonClient.Fs; + + string source = DateTime.Now.ToFileTime().ToString()[10..]; + string output = DateTime.Now.ToFileTime().ToString()[10..]; + + using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem); + using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination)); + + fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); + fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); + + (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token); + + if (!canceled) + { + if (resultCode.Value.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]); + }); + } + else if (resultCode.Value.IsSuccess()) + { + Dispatcher.UIThread.Post(waitingDialog.Close); + + NotificationHelper.Show( + LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle], + $"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}", + NotificationType.Information); + } + } + + fsClient.Unmount(source.ToU8Span()); + fsClient.Unmount(output.ToU8Span()); + } + catch (ArgumentException ex) + { + Logger.Error?.Print(LogClass.Application, $"{ex.Message}"); + + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(ex.Message); + }); + } + }) + { + Name = "GUI.NcaSectionExtractorThread", + IsBackground = true, + }; + extractorThread.Start(); + } + + public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token) + { + Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) + { + return (rc, false); + } + + using (sourceHandle) + { + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) + { + if (token.IsCancellationRequested) + { + return (null, true); + } + + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) + { + fs.EnsureDirectoryExists(subDstPath); + + (Result? result, bool canceled) = CopyDirectory(fs, subSrcPath, subDstPath, token); + if (canceled || result.Value.IsFailure()) + { + return (result, canceled); + } + } + + if (entry.Type == DirectoryEntryType.File) + { + fs.CreateOrOverwriteFile(subDstPath, entry.Size); + + rc = CopyFile(fs, subSrcPath, subDstPath); + if (rc.IsFailure()) + { + return (rc, false); + } + } + } + } + + return (Result.Success, false); + } + + public static Result CopyFile(FileSystemClient fs, string sourcePath, string destPath) + { + Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); + if (rc.IsFailure()) + { + return rc; + } + + using (sourceHandle) + { + rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend); + if (rc.IsFailure()) + { + return rc; + } + + using (destHandle) + { + const int MaxBufferSize = 1024 * 1024; + + rc = fs.GetFileSize(out long fileSize, sourceHandle); + if (rc.IsFailure()) + { + return rc; + } + + int bufferSize = (int)Math.Min(MaxBufferSize, fileSize); + + byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); + try + { + for (long offset = 0; offset < fileSize; offset += bufferSize) + { + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span<byte> buf = buffer.AsSpan(0, toRead); + + rc = fs.ReadFile(out long _, sourceHandle, offset, buf); + if (rc.IsFailure()) + { + return rc; + } + + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); + if (rc.IsFailure()) + { + return rc; + } + } + } + finally + { + ArrayPool<byte>.Shared.Return(buffer); + } + + rc = fs.FlushFile(destHandle); + if (rc.IsFailure()) + { + return rc; + } + } + } + + return Result.Success; + } + } +} diff --git a/src/Ryujinx/Common/ApplicationSort.cs b/src/Ryujinx/Common/ApplicationSort.cs new file mode 100644 index 00000000..4b80e3d2 --- /dev/null +++ b/src/Ryujinx/Common/ApplicationSort.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Ava.Common +{ + internal enum ApplicationSort + { + Title, + TitleId, + Developer, + LastPlayed, + TotalTimePlayed, + FileType, + FileSize, + Path, + Favorite, + } +} diff --git a/src/Ryujinx/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs new file mode 100644 index 00000000..6e492098 --- /dev/null +++ b/src/Ryujinx/Common/KeyboardHotkeyState.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Ava.Common +{ + public enum KeyboardHotkeyState + { + None, + ToggleVSync, + Screenshot, + ShowUI, + Pause, + ToggleMute, + ResScaleUp, + ResScaleDown, + VolumeUp, + VolumeDown, + } +} diff --git a/src/Ryujinx/Common/Locale/LocaleExtension.cs b/src/Ryujinx/Common/Locale/LocaleExtension.cs new file mode 100644 index 00000000..40661bf3 --- /dev/null +++ b/src/Ryujinx/Common/Locale/LocaleExtension.cs @@ -0,0 +1,40 @@ +using Avalonia.Data.Core; +using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; +using System; + +namespace Ryujinx.Ava.Common.Locale +{ + internal class LocaleExtension : MarkupExtension + { + public LocaleExtension(LocaleKeys key) + { + Key = key; + } + + public LocaleKeys Key { get; } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + LocaleKeys keyToUse = Key; + + var builder = new CompiledBindingPathBuilder(); + + builder.SetRawSource(LocaleManager.Instance) + .Property(new ClrPropertyInfo("Item", + obj => (LocaleManager.Instance[keyToUse]), + null, + typeof(string)), (weakRef, iPropInfo) => + { + return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo); + }); + + var path = builder.Build(); + + var binding = new CompiledBindingExtension(path); + + return binding.ProvideValue(serviceProvider); + } + } +} diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs new file mode 100644 index 00000000..e973fcc0 --- /dev/null +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -0,0 +1,160 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; + +namespace Ryujinx.Ava.Common.Locale +{ + class LocaleManager : BaseModel + { + private const string DefaultLanguageCode = "en_US"; + + private readonly Dictionary<LocaleKeys, string> _localeStrings; + private Dictionary<LocaleKeys, string> _localeDefaultStrings; + private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues; + private string _localeLanguageCode; + + public static LocaleManager Instance { get; } = new(); + public event Action LocaleChanged; + + public LocaleManager() + { + _localeStrings = new Dictionary<LocaleKeys, string>(); + _localeDefaultStrings = new Dictionary<LocaleKeys, string>(); + _dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>(); + + Load(); + } + + public void Load() + { + // Load the system Language Code. + string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_'); + + // 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)) + { + localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value; + } + else + { + localeLanguageCode = DefaultLanguageCode; + } + } + + // Load en_US as default, if the target language translation is incomplete. + LoadDefaultLanguage(); + + LoadLanguage(localeLanguageCode); + } + + public string this[LocaleKeys key] + { + get + { + // Check if the locale contains the key. + if (_localeStrings.TryGetValue(key, out string value)) + { + // Check if the localized string needs to be formatted. + if (_dynamicValues.TryGetValue(key, out var dynamicValue)) + { + try + { + return string.Format(value, dynamicValue); + } + catch (Exception) + { + // If formatting failed use the default text instead. + if (_localeDefaultStrings.TryGetValue(key, out value)) + { + try + { + return string.Format(value, dynamicValue); + } + catch (Exception) + { + // If formatting the default text failed return the key. + return key.ToString(); + } + } + } + } + + return value; + } + + // If the locale doesn't contain the key return the default one. + if (_localeDefaultStrings.TryGetValue(key, out string defaultValue)) + { + return defaultValue; + } + + // If the locale text doesn't exist return the key. + return key.ToString(); + } + set + { + _localeStrings[key] = value; + + OnPropertyChanged(); + } + } + + public bool IsRTL() + { + return _localeLanguageCode switch + { + "he_IL" => true, + _ => false + }; + } + + public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values) + { + _dynamicValues[key] = values; + + OnPropertyChanged("Item"); + + return this[key]; + } + + private void LoadDefaultLanguage() + { + _localeDefaultStrings = LoadJsonLanguage(); + } + + public void LoadLanguage(string languageCode) + { + foreach (var item in LoadJsonLanguage(languageCode)) + { + this[item.Key] = item.Value; + } + + _localeLanguageCode = languageCode; + LocaleChanged?.Invoke(); + } + + private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode = DefaultLanguageCode) + { + var localeStrings = new Dictionary<LocaleKeys, string>(); + string languageJson = EmbeddedResources.ReadAllText($"Ryujinx/Assets/Locales/{languageCode}.json"); + var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); + + foreach (var item in strings) + { + if (Enum.TryParse<LocaleKeys>(item.Key, out var key)) + { + localeStrings[key] = item.Value; + } + } + + return localeStrings; + } + } +} diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs new file mode 100644 index 00000000..fbaaaaba --- /dev/null +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -0,0 +1,203 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Input; +using System; +using System.Collections.Generic; +using System.Numerics; +using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; +using Key = Ryujinx.Input.Key; + +namespace Ryujinx.Ava.Input +{ + internal class AvaloniaKeyboard : IKeyboard + { + private readonly List<ButtonMappingEntry> _buttonsUserMapping; + private readonly AvaloniaKeyboardDriver _driver; + private StandardKeyboardInputConfig _configuration; + + private readonly object _userMappingLock = new(); + + public string Id { get; } + public string Name { get; } + + public bool IsConnected => true; + public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; + + private class ButtonMappingEntry + { + public readonly Key From; + public readonly GamepadButtonInputId To; + + public ButtonMappingEntry(GamepadButtonInputId to, Key from) + { + To = to; + From = from; + } + } + + public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name) + { + _buttonsUserMapping = new List<ButtonMappingEntry>(); + + _driver = driver; + Id = id; + Name = name; + } + + public KeyboardStateSnapshot GetKeyboardStateSnapshot() + { + return IKeyboard.GetStateSnapshot(this); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (_configuration == null) + { + return result; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + continue; + } + + // NOTE: Do not touch state of the button already pressed. + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + + result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); + result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); + } + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotSupportedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(Key key) + { + try + { + return _driver.IsPressed(key); + } + catch + { + return false; + } + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardKeyboardInputConfig)configuration; + + _buttonsUserMapping.Clear(); + +#pragma warning disable IDE0055 // Disable formatting + // Left JoyCon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + + // Right JoyCon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); +#pragma warning restore IDE0055 + } + } + + public void SetTriggerThreshold(float triggerThreshold) { } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { } + + public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero; + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig) + { + short stickX = 0; + short stickY = 0; + + if (snapshot.IsPressed((Key)stickConfig.StickUp)) + { + stickY += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickDown)) + { + stickY -= 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickRight)) + { + stickX += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + { + stickX -= 1; + } + + Vector2 stick = new(stickX, stickY); + + stick = Vector2.Normalize(stick); + + return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); + } + + public void Clear() + { + _driver?.ResetKeys(); + } + + public void Dispose() { } + } +} diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs new file mode 100644 index 00000000..e9e71b99 --- /dev/null +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -0,0 +1,107 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Input; +using System; +using System.Collections.Generic; +using AvaKey = Avalonia.Input.Key; +using Key = Ryujinx.Input.Key; + +namespace Ryujinx.Ava.Input +{ + internal class AvaloniaKeyboardDriver : IGamepadDriver + { + private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; + private readonly Control _control; + private readonly HashSet<AvaKey> _pressedKeys; + + public event EventHandler<KeyEventArgs> KeyPressed; + public event EventHandler<KeyEventArgs> KeyRelease; + public event EventHandler<string> TextInput; + + public string DriverName => "AvaloniaKeyboardDriver"; + public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; + + public AvaloniaKeyboardDriver(Control control) + { + _control = control; + _pressedKeys = new HashSet<AvaKey>(); + + _control.KeyDown += OnKeyPress; + _control.KeyUp += OnKeyRelease; + _control.TextInput += Control_TextInput; + } + + private void Control_TextInput(object sender, TextInputEventArgs e) + { + TextInput?.Invoke(this, e.Text); + } + + public event Action<string> OnGamepadConnected + { + add { } + remove { } + } + + public event Action<string> OnGamepadDisconnected + { + add { } + remove { } + } + + public IGamepad GetGamepad(string id) + { + if (!_keyboardIdentifers[0].Equals(id)) + { + return null; + } + + return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _control.KeyUp -= OnKeyPress; + _control.KeyDown -= OnKeyRelease; + } + } + + protected void OnKeyPress(object sender, KeyEventArgs args) + { + _pressedKeys.Add(args.Key); + + KeyPressed?.Invoke(this, args); + } + + protected void OnKeyRelease(object sender, KeyEventArgs args) + { + _pressedKeys.Remove(args.Key); + + KeyRelease?.Invoke(this, args); + } + + internal bool IsPressed(Key key) + { + if (key == Key.Unbound || key == Key.Unknown) + { + return false; + } + + AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey); + + return _pressedKeys.Contains(nativeKey); + } + + public void ResetKeys() + { + _pressedKeys.Clear(); + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs new file mode 100644 index 00000000..97ebd721 --- /dev/null +++ b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs @@ -0,0 +1,185 @@ +using Ryujinx.Input; +using System; +using System.Collections.Generic; +using AvaKey = Avalonia.Input.Key; + +namespace Ryujinx.Ava.Input +{ + internal static class AvaloniaKeyboardMappingHelper + { + private static readonly AvaKey[] _keyMapping = { + // NOTE: Invalid + AvaKey.None, + + AvaKey.LeftShift, + AvaKey.RightShift, + AvaKey.LeftCtrl, + AvaKey.RightCtrl, + AvaKey.LeftAlt, + AvaKey.RightAlt, + AvaKey.LWin, + AvaKey.RWin, + AvaKey.Apps, + AvaKey.F1, + AvaKey.F2, + AvaKey.F3, + AvaKey.F4, + AvaKey.F5, + AvaKey.F6, + AvaKey.F7, + AvaKey.F8, + AvaKey.F9, + AvaKey.F10, + AvaKey.F11, + AvaKey.F12, + AvaKey.F13, + AvaKey.F14, + AvaKey.F15, + AvaKey.F16, + AvaKey.F17, + AvaKey.F18, + AvaKey.F19, + AvaKey.F20, + AvaKey.F21, + AvaKey.F22, + AvaKey.F23, + AvaKey.F24, + + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + AvaKey.None, + + AvaKey.Up, + AvaKey.Down, + AvaKey.Left, + AvaKey.Right, + AvaKey.Return, + AvaKey.Escape, + AvaKey.Space, + AvaKey.Tab, + AvaKey.Back, + AvaKey.Insert, + AvaKey.Delete, + AvaKey.PageUp, + AvaKey.PageDown, + AvaKey.Home, + AvaKey.End, + AvaKey.CapsLock, + AvaKey.Scroll, + AvaKey.Print, + AvaKey.Pause, + AvaKey.NumLock, + AvaKey.Clear, + AvaKey.NumPad0, + AvaKey.NumPad1, + AvaKey.NumPad2, + AvaKey.NumPad3, + AvaKey.NumPad4, + AvaKey.NumPad5, + AvaKey.NumPad6, + AvaKey.NumPad7, + AvaKey.NumPad8, + AvaKey.NumPad9, + AvaKey.Divide, + AvaKey.Multiply, + AvaKey.Subtract, + AvaKey.Add, + AvaKey.Decimal, + AvaKey.Enter, + AvaKey.A, + AvaKey.B, + AvaKey.C, + AvaKey.D, + AvaKey.E, + AvaKey.F, + AvaKey.G, + AvaKey.H, + AvaKey.I, + AvaKey.J, + AvaKey.K, + AvaKey.L, + AvaKey.M, + AvaKey.N, + AvaKey.O, + AvaKey.P, + AvaKey.Q, + AvaKey.R, + AvaKey.S, + AvaKey.T, + AvaKey.U, + AvaKey.V, + AvaKey.W, + AvaKey.X, + AvaKey.Y, + AvaKey.Z, + AvaKey.D0, + AvaKey.D1, + AvaKey.D2, + AvaKey.D3, + AvaKey.D4, + AvaKey.D5, + AvaKey.D6, + AvaKey.D7, + AvaKey.D8, + AvaKey.D9, + AvaKey.OemTilde, + AvaKey.OemTilde,AvaKey.OemMinus, + AvaKey.OemPlus, + AvaKey.OemOpenBrackets, + AvaKey.OemCloseBrackets, + AvaKey.OemSemicolon, + AvaKey.OemQuotes, + AvaKey.OemComma, + AvaKey.OemPeriod, + AvaKey.OemQuestion, + AvaKey.OemBackslash, + + // NOTE: invalid + AvaKey.None, + }; + + private static readonly Dictionary<AvaKey, Key> _avaKeyMapping; + + static AvaloniaKeyboardMappingHelper() + { + var inputKeys = Enum.GetValues<Key>(); + + // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array. + _avaKeyMapping = new Dictionary<AvaKey, Key>(); + + foreach (var key in inputKeys) + { + if (TryGetAvaKey(key, out var index)) + { + _avaKeyMapping[index] = key; + } + } + } + + public static bool TryGetAvaKey(Key key, out AvaKey avaKey) + { + avaKey = AvaKey.None; + + bool keyExist = (int)key < _keyMapping.Length; + if (keyExist) + { + avaKey = _keyMapping[(int)key]; + } + + return keyExist; + } + + public static Key ToInputKey(AvaKey key) + { + return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); + } + } +} diff --git a/src/Ryujinx/Input/AvaloniaMouse.cs b/src/Ryujinx/Input/AvaloniaMouse.cs new file mode 100644 index 00000000..1aa2d586 --- /dev/null +++ b/src/Ryujinx/Input/AvaloniaMouse.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Input; +using System; +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Ava.Input +{ + internal class AvaloniaMouse : IMouse + { + private AvaloniaMouseDriver _driver; + + public string Id => "0"; + public string Name => "AvaloniaMouse"; + + public bool IsConnected => true; + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + public bool[] Buttons => _driver.PressedButtons; + + public AvaloniaMouse(AvaloniaMouseDriver driver) + { + _driver = driver; + } + + public Size ClientSize => _driver.GetClientSize(); + + public Vector2 GetPosition() + { + return _driver.CurrentPosition; + } + + public Vector2 GetScroll() + { + return _driver.Scroll; + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return _driver.IsButtonPressed(button); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + _driver = null; + } + } +} diff --git a/src/Ryujinx/Input/AvaloniaMouseDriver.cs b/src/Ryujinx/Input/AvaloniaMouseDriver.cs new file mode 100644 index 00000000..e71bbf64 --- /dev/null +++ b/src/Ryujinx/Input/AvaloniaMouseDriver.cs @@ -0,0 +1,159 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Ryujinx.Input; +using System; +using System.Numerics; +using MouseButton = Ryujinx.Input.MouseButton; +using Size = System.Drawing.Size; + +namespace Ryujinx.Ava.Input +{ + internal class AvaloniaMouseDriver : IGamepadDriver + { + private Control _widget; + private bool _isDisposed; + private Size _size; + private readonly TopLevel _window; + + public bool[] PressedButtons { get; } + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + + public string DriverName => "AvaloniaMouseDriver"; + public ReadOnlySpan<string> GamepadsIds => new[] { "0" }; + + public AvaloniaMouseDriver(TopLevel window, Control parent) + { + _widget = parent; + _window = window; + + _widget.PointerMoved += Parent_PointerMovedEvent; + _widget.PointerPressed += Parent_PointerPressedEvent; + _widget.PointerReleased += Parent_PointerReleasedEvent; + _widget.PointerWheelChanged += Parent_PointerWheelChanged; + + _window.PointerMoved += Parent_PointerMovedEvent; + _window.PointerPressed += Parent_PointerPressedEvent; + _window.PointerReleased += Parent_PointerReleasedEvent; + _window.PointerWheelChanged += Parent_PointerWheelChanged; + + PressedButtons = new bool[(int)MouseButton.Count]; + + _size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height); + + parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized); + } + + public event Action<string> OnGamepadConnected + { + add { } + remove { } + } + + public event Action<string> OnGamepadDisconnected + { + add { } + remove { } + } + + private void Resized(Rect rect) + { + _size = new Size((int)rect.Width, (int)rect.Height); + } + + private void Parent_PointerWheelChanged(object o, PointerWheelEventArgs args) + { + Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y); + } + + private void Parent_PointerReleasedEvent(object o, PointerReleasedEventArgs args) + { + uint button = (uint)args.InitialPressMouseButton - 1; + + if ((uint)PressedButtons.Length > button) + { + PressedButtons[button] = false; + } + } + private void Parent_PointerPressedEvent(object o, PointerPressedEventArgs args) + { + uint button = (uint)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind; + + if ((uint)PressedButtons.Length > button) + { + PressedButtons[button] = true; + } + } + + private void Parent_PointerMovedEvent(object o, PointerEventArgs args) + { + Point position = args.GetPosition(_widget); + + CurrentPosition = new Vector2((float)position.X, (float)position.Y); + } + + public void SetMousePressed(MouseButton button) + { + if ((uint)PressedButtons.Length > (uint)button) + { + PressedButtons[(uint)button] = true; + } + } + + public void SetMouseReleased(MouseButton button) + { + if ((uint)PressedButtons.Length > (uint)button) + { + PressedButtons[(uint)button] = false; + } + } + + public void SetPosition(double x, double y) + { + CurrentPosition = new Vector2((float)x, (float)y); + } + + public bool IsButtonPressed(MouseButton button) + { + if ((uint)PressedButtons.Length > (uint)button) + { + return PressedButtons[(uint)button]; + } + + return false; + } + + public Size GetClientSize() + { + return _size; + } + + public IGamepad GetGamepad(string id) + { + return new AvaloniaMouse(this); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + _widget.PointerMoved -= Parent_PointerMovedEvent; + _widget.PointerPressed -= Parent_PointerPressedEvent; + _widget.PointerReleased -= Parent_PointerReleasedEvent; + _widget.PointerWheelChanged -= Parent_PointerWheelChanged; + + _window.PointerMoved -= Parent_PointerMovedEvent; + _window.PointerPressed -= Parent_PointerPressedEvent; + _window.PointerReleased -= Parent_PointerReleasedEvent; + _window.PointerWheelChanged -= Parent_PointerWheelChanged; + + _widget = null; + } + } +} diff --git a/src/Ryujinx/Input/GTK3/GTK3Keyboard.cs b/src/Ryujinx/Input/GTK3/GTK3Keyboard.cs deleted file mode 100644 index ff7a2c3b..00000000 --- a/src/Ryujinx/Input/GTK3/GTK3Keyboard.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using System; -using System.Collections.Generic; -using System.Numerics; -using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; - -namespace Ryujinx.Input.GTK3 -{ - public class GTK3Keyboard : IKeyboard - { - private class ButtonMappingEntry - { - public readonly GamepadButtonInputId To; - public readonly Key From; - - public ButtonMappingEntry(GamepadButtonInputId to, Key from) - { - To = to; - From = from; - } - } - - private readonly object _userMappingLock = new(); - - private readonly GTK3KeyboardDriver _driver; - private StandardKeyboardInputConfig _configuration; - private readonly List<ButtonMappingEntry> _buttonsUserMapping; - - public GTK3Keyboard(GTK3KeyboardDriver driver, string id, string name) - { - _driver = driver; - Id = id; - Name = name; - _buttonsUserMapping = new List<ButtonMappingEntry>(); - } - - private bool HasConfiguration => _configuration != null; - - public string Id { get; } - - public string Name { get; } - - public bool IsConnected => true; - - public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; - - public void Dispose() - { - // No operations - GC.SuppressFinalize(this); - } - - public KeyboardStateSnapshot GetKeyboardStateSnapshot() - { - return IKeyboard.GetStateSnapshot(this); - } - - private static float ConvertRawStickValue(short value) - { - const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); - - return value * ConvertRate; - } - - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig) - { - short stickX = 0; - short stickY = 0; - - if (snapshot.IsPressed((Key)stickConfig.StickUp)) - { - stickY += 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickDown)) - { - stickY -= 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickRight)) - { - stickX += 1; - } - - if (snapshot.IsPressed((Key)stickConfig.StickLeft)) - { - stickX -= 1; - } - - OpenTK.Mathematics.Vector2 stick = new(stickX, stickY); - - stick.NormalizeFast(); - - return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); - } - - public GamepadStateSnapshot GetMappedStateSnapshot() - { - KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); - GamepadStateSnapshot result = default; - - lock (_userMappingLock) - { - if (!HasConfiguration) - { - return result; - } - - foreach (ButtonMappingEntry entry in _buttonsUserMapping) - { - if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) - { - continue; - } - - // Do not touch state of button already pressed - if (!result.IsPressed(entry.To)) - { - result.SetPressed(entry.To, rawState.IsPressed(entry.From)); - } - } - - (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); - (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); - - result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); - result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); - } - - return result; - } - - public GamepadStateSnapshot GetStateSnapshot() - { - throw new NotSupportedException(); - } - - public (float, float) GetStick(StickInputId inputId) - { - throw new NotSupportedException(); - } - - public bool IsPressed(GamepadButtonInputId inputId) - { - throw new NotSupportedException(); - } - - public bool IsPressed(Key key) - { - return _driver.IsPressed(key); - } - - public void SetConfiguration(InputConfig configuration) - { - lock (_userMappingLock) - { - _configuration = (StandardKeyboardInputConfig)configuration; - - _buttonsUserMapping.Clear(); - - // Then left joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); - - // Finally right joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); - } - } - - public void SetTriggerThreshold(float triggerThreshold) - { - // No operations - } - - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) - { - // No operations - } - - public Vector3 GetMotionData(MotionInputId inputId) - { - // No operations - - return Vector3.Zero; - } - } -} diff --git a/src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs deleted file mode 100644 index e502254b..00000000 --- a/src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Gdk; -using Gtk; -using System; -using System.Collections.Generic; -using GtkKey = Gdk.Key; - -namespace Ryujinx.Input.GTK3 -{ - public class GTK3KeyboardDriver : IGamepadDriver - { - private readonly Widget _widget; - private readonly HashSet<GtkKey> _pressedKeys; - - public GTK3KeyboardDriver(Widget widget) - { - _widget = widget; - _pressedKeys = new HashSet<GtkKey>(); - - _widget.KeyPressEvent += OnKeyPress; - _widget.KeyReleaseEvent += OnKeyRelease; - } - - public string DriverName => "GTK3"; - - private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; - - public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; - - public event Action<string> OnGamepadConnected - { - add { } - remove { } - } - - public event Action<string> OnGamepadDisconnected - { - add { } - remove { } - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _widget.KeyPressEvent -= OnKeyPress; - _widget.KeyReleaseEvent -= OnKeyRelease; - } - } - - public void Dispose() - { - GC.SuppressFinalize(this); - Dispose(true); - } - - [GLib.ConnectBefore] - protected void OnKeyPress(object sender, KeyPressEventArgs args) - { - GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); - - _pressedKeys.Add(key); - } - - [GLib.ConnectBefore] - protected void OnKeyRelease(object sender, KeyReleaseEventArgs args) - { - GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); - - _pressedKeys.Remove(key); - } - - internal bool IsPressed(Key key) - { - if (key == Key.Unbound || key == Key.Unknown) - { - return false; - } - - GtkKey nativeKey = GTK3MappingHelper.ToGtkKey(key); - - return _pressedKeys.Contains(nativeKey); - } - - public IGamepad GetGamepad(string id) - { - if (!_keyboardIdentifers[0].Equals(id)) - { - return null; - } - - return new GTK3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); - } - } -} diff --git a/src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs b/src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs deleted file mode 100644 index 422a9603..00000000 --- a/src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using GtkKey = Gdk.Key; - -namespace Ryujinx.Input.GTK3 -{ - public static class GTK3MappingHelper - { - private static readonly GtkKey[] _keyMapping = new GtkKey[(int)Key.Count] - { - // NOTE: invalid - GtkKey.blank, - - GtkKey.Shift_L, - GtkKey.Shift_R, - GtkKey.Control_L, - GtkKey.Control_R, - GtkKey.Alt_L, - GtkKey.Alt_R, - GtkKey.Super_L, - GtkKey.Super_R, - GtkKey.Menu, - GtkKey.F1, - GtkKey.F2, - GtkKey.F3, - GtkKey.F4, - GtkKey.F5, - GtkKey.F6, - GtkKey.F7, - GtkKey.F8, - GtkKey.F9, - GtkKey.F10, - GtkKey.F11, - GtkKey.F12, - GtkKey.F13, - GtkKey.F14, - GtkKey.F15, - GtkKey.F16, - GtkKey.F17, - GtkKey.F18, - GtkKey.F19, - GtkKey.F20, - GtkKey.F21, - GtkKey.F22, - GtkKey.F23, - GtkKey.F24, - GtkKey.F25, - GtkKey.F26, - GtkKey.F27, - GtkKey.F28, - GtkKey.F29, - GtkKey.F30, - GtkKey.F31, - GtkKey.F32, - GtkKey.F33, - GtkKey.F34, - GtkKey.F35, - GtkKey.Up, - GtkKey.Down, - GtkKey.Left, - GtkKey.Right, - GtkKey.Return, - GtkKey.Escape, - GtkKey.space, - GtkKey.Tab, - GtkKey.BackSpace, - GtkKey.Insert, - GtkKey.Delete, - GtkKey.Page_Up, - GtkKey.Page_Down, - GtkKey.Home, - GtkKey.End, - GtkKey.Caps_Lock, - GtkKey.Scroll_Lock, - GtkKey.Print, - GtkKey.Pause, - GtkKey.Num_Lock, - GtkKey.Clear, - GtkKey.KP_0, - GtkKey.KP_1, - GtkKey.KP_2, - GtkKey.KP_3, - GtkKey.KP_4, - GtkKey.KP_5, - GtkKey.KP_6, - GtkKey.KP_7, - GtkKey.KP_8, - GtkKey.KP_9, - GtkKey.KP_Divide, - GtkKey.KP_Multiply, - GtkKey.KP_Subtract, - GtkKey.KP_Add, - GtkKey.KP_Decimal, - GtkKey.KP_Enter, - GtkKey.a, - GtkKey.b, - GtkKey.c, - GtkKey.d, - GtkKey.e, - GtkKey.f, - GtkKey.g, - GtkKey.h, - GtkKey.i, - GtkKey.j, - GtkKey.k, - GtkKey.l, - GtkKey.m, - GtkKey.n, - GtkKey.o, - GtkKey.p, - GtkKey.q, - GtkKey.r, - GtkKey.s, - GtkKey.t, - GtkKey.u, - GtkKey.v, - GtkKey.w, - GtkKey.x, - GtkKey.y, - GtkKey.z, - GtkKey.Key_0, - GtkKey.Key_1, - GtkKey.Key_2, - GtkKey.Key_3, - GtkKey.Key_4, - GtkKey.Key_5, - GtkKey.Key_6, - GtkKey.Key_7, - GtkKey.Key_8, - GtkKey.Key_9, - GtkKey.grave, - GtkKey.grave, - GtkKey.minus, - GtkKey.plus, - GtkKey.bracketleft, - GtkKey.bracketright, - GtkKey.semicolon, - GtkKey.quoteright, - GtkKey.comma, - GtkKey.period, - GtkKey.slash, - GtkKey.backslash, - - // NOTE: invalid - GtkKey.blank, - }; - - private static readonly Dictionary<GtkKey, Key> _gtkKeyMapping; - - static GTK3MappingHelper() - { - var inputKeys = Enum.GetValues<Key>().SkipLast(1); - - // GtkKey is not contiguous and quite large, so use a dictionary instead of an array. - _gtkKeyMapping = new Dictionary<GtkKey, Key>(); - - foreach (var key in inputKeys) - { - var index = ToGtkKey(key); - _gtkKeyMapping[index] = key; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GtkKey ToGtkKey(Key key) - { - return _keyMapping[(int)key]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Key ToInputKey(GtkKey key) - { - return _gtkKeyMapping.GetValueOrDefault(key, Key.Unknown); - } - } -} diff --git a/src/Ryujinx/Input/GTK3/GTK3Mouse.cs b/src/Ryujinx/Input/GTK3/GTK3Mouse.cs deleted file mode 100644 index 0ab817ec..00000000 --- a/src/Ryujinx/Input/GTK3/GTK3Mouse.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Ryujinx.Common.Configuration.Hid; -using System; -using System.Drawing; -using System.Numerics; - -namespace Ryujinx.Input.GTK3 -{ - public class GTK3Mouse : IMouse - { - private GTK3MouseDriver _driver; - - public GamepadFeaturesFlag Features => throw new NotImplementedException(); - - public string Id => "0"; - - public string Name => "GTKMouse"; - - public bool IsConnected => true; - - public bool[] Buttons => _driver.PressedButtons; - - public GTK3Mouse(GTK3MouseDriver driver) - { - _driver = driver; - } - - public Size ClientSize => _driver.GetClientSize(); - - public Vector2 GetPosition() - { - return _driver.CurrentPosition; - } - - public Vector2 GetScroll() - { - return _driver.Scroll; - } - - public GamepadStateSnapshot GetMappedStateSnapshot() - { - throw new NotImplementedException(); - } - - public Vector3 GetMotionData(MotionInputId inputId) - { - throw new NotImplementedException(); - } - - public GamepadStateSnapshot GetStateSnapshot() - { - throw new NotImplementedException(); - } - - public (float, float) GetStick(StickInputId inputId) - { - throw new NotImplementedException(); - } - - public bool IsButtonPressed(MouseButton button) - { - return _driver.IsButtonPressed(button); - } - - public bool IsPressed(GamepadButtonInputId inputId) - { - throw new NotImplementedException(); - } - - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) - { - throw new NotImplementedException(); - } - - public void SetConfiguration(InputConfig configuration) - { - throw new NotImplementedException(); - } - - public void SetTriggerThreshold(float triggerThreshold) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _driver = null; - } - } -} diff --git a/src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs b/src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs deleted file mode 100644 index 5962bcb2..00000000 --- a/src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Gdk; -using Gtk; -using System; -using System.Numerics; -using Size = System.Drawing.Size; - -namespace Ryujinx.Input.GTK3 -{ - public class GTK3MouseDriver : IGamepadDriver - { - private Widget _widget; - private bool _isDisposed; - - public bool[] PressedButtons { get; } - - public Vector2 CurrentPosition { get; private set; } - public Vector2 Scroll { get; private set; } - - public GTK3MouseDriver(Widget parent) - { - _widget = parent; - - _widget.MotionNotifyEvent += Parent_MotionNotifyEvent; - _widget.ButtonPressEvent += Parent_ButtonPressEvent; - _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent; - _widget.ScrollEvent += Parent_ScrollEvent; - - PressedButtons = new bool[(int)MouseButton.Count]; - } - - - [GLib.ConnectBefore] - private void Parent_ScrollEvent(object o, ScrollEventArgs args) - { - Scroll = new Vector2((float)args.Event.X, (float)args.Event.Y); - } - - [GLib.ConnectBefore] - private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args) - { - PressedButtons[args.Event.Button - 1] = false; - } - - [GLib.ConnectBefore] - private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args) - { - PressedButtons[args.Event.Button - 1] = true; - } - - [GLib.ConnectBefore] - private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args) - { - if (args.Event.Device.InputSource == InputSource.Mouse) - { - CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y); - } - } - - public bool IsButtonPressed(MouseButton button) - { - return PressedButtons[(int)button]; - } - - public Size GetClientSize() - { - return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight); - } - - public string DriverName => "GTK3"; - - public event Action<string> OnGamepadConnected - { - add { } - remove { } - } - - public event Action<string> OnGamepadDisconnected - { - add { } - remove { } - } - - public ReadOnlySpan<string> GamepadsIds => new[] { "0" }; - - public IGamepad GetGamepad(string id) - { - return new GTK3Mouse(this); - } - - public void Dispose() - { - if (_isDisposed) - { - return; - } - - GC.SuppressFinalize(this); - - _isDisposed = true; - - _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent; - _widget.ButtonPressEvent -= Parent_ButtonPressEvent; - _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent; - - _widget = null; - } - } -} diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.cs b/src/Ryujinx/Modules/Updater/UpdateDialog.cs deleted file mode 100644 index ec24cdc8..00000000 --- a/src/Ryujinx/Modules/Updater/UpdateDialog.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Gdk; -using Gtk; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.UI; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using System; -using System.Diagnostics; -using System.Reflection; - -namespace Ryujinx.Modules -{ - public class UpdateDialog : Gtk.Window - { -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [Builder.Object] public Label MainText; - [Builder.Object] public Label SecondaryText; - [Builder.Object] public LevelBar ProgressBar; - [Builder.Object] public Button YesButton; - [Builder.Object] public Button NoButton; -#pragma warning restore CS0649, IDE0044 - - private readonly MainWindow _mainWindow; - private readonly string _buildUrl; - private bool _restartQuery; - - public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } - - private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) - { - builder.Autoconnect(this); - - _mainWindow = mainWindow; - _buildUrl = buildUrl; - - Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - MainText.Text = "Do you want to update Ryujinx to the latest version?"; - SecondaryText.Text = $"{Program.Version} -> {newVersion}"; - - ProgressBar.Hide(); - - YesButton.Clicked += YesButton_Clicked; - NoButton.Clicked += NoButton_Clicked; - } - - private void YesButton_Clicked(object sender, EventArgs args) - { - if (_restartQuery) - { - string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; - - ProcessStartInfo processStart = new(ryuName) - { - UseShellExecute = true, - WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory - }; - - foreach (string argument in CommandLineState.Arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - - Environment.Exit(0); - } - else - { - Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; - _mainWindow.ExitMenuItem.Sensitive = false; - - YesButton.Hide(); - NoButton.Hide(); - ProgressBar.Show(); - - SecondaryText.Text = ""; - _restartQuery = true; - - Updater.UpdateRyujinx(this, _buildUrl); - } - } - - private void NoButton_Clicked(object sender, EventArgs args) - { - Updater.Running = false; - _mainWindow.Window.Functions = WMFunction.All; - - _mainWindow.ExitMenuItem.Sensitive = true; - _mainWindow.UpdateMenuItem.Sensitive = true; - - Dispose(); - } - } -} diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.glade b/src/Ryujinx/Modules/Updater/UpdateDialog.glade deleted file mode 100644 index cc80167e..00000000 --- a/src/Ryujinx/Modules/Updater/UpdateDialog.glade +++ /dev/null @@ -1,127 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkWindow" id="UpdateDialog"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - Updater</property> - <property name="resizable">False</property> - <property name="window_position">center</property> - <property name="default_width">400</property> - <property name="default_height">130</property> - <child> - <placeholder/> - </child> - <child> - <object class="GtkBox" id="MainBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="BodyBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel" id="MainText"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <attributes> - <attribute name="weight" value="bold"/> - <attribute name="size" value="10000"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="SecondaryText"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkLevelBar" id="ProgressBar"> - <property name="height_request">20</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="max_value">100</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="ButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkButton" id="YesButton"> - <property name="label" translatable="yes">Yes</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">5</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="NoButton"> - <property name="label" translatable="yes">No</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_left">5</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 6c0f9cce..d8346c8e 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -1,93 +1,75 @@ -using Gtk; +using Avalonia.Controls; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.UI; +using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Models.Github; -using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Ryujinx.Modules { - public static class Updater + internal static class Updater { private const string GitHubApiUrl = "https://api.github.com"; - private const int ConnectionCount = 4; - - internal static bool Running; + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); + private const int ConnectionCount = 4; private static string _buildVer; private static string _platformExt; private static string _buildUrl; private static long _buildSize; + private static bool _updateSuccessful; + private static bool _running; - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. - private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" }; + private static readonly string[] _windowsDependencyDirs = Array.Empty<string>(); - private static HttpClient ConstructHttpClient() + public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) { - HttpClient result = new(); - - // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); - - return result; - } - - public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) - { - if (Running) + if (_running) { return; } - Running = true; - mainWindow.UpdateMenuItem.Sensitive = false; - - int artifactIndex = -1; + _running = true; // Detect current platform if (OperatingSystem.IsMacOS()) { - _platformExt = "osx_x64.zip"; - artifactIndex = 1; + _platformExt = "macos_universal.app.tar.gz"; } else if (OperatingSystem.IsWindows()) { _platformExt = "win_x64.zip"; - artifactIndex = 2; } else if (OperatingSystem.IsLinux()) { var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; _platformExt = $"linux_{arch}.tar.gz"; - artifactIndex = 0; - } - - if (artifactIndex == -1) - { - GtkDialog.CreateErrorDialog("Your platform is not supported!"); - - return; } Version newVersion; @@ -99,9 +81,14 @@ namespace Ryujinx.Modules } catch { - GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + return; } @@ -109,9 +96,8 @@ namespace Ryujinx.Modules try { using HttpClient jsonClient = ConstructHttpClient(); - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - // Fetch latest build information + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); _buildVer = fetched.Name; @@ -126,9 +112,13 @@ namespace Ryujinx.Modules { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } + _running = false; + return; } @@ -136,20 +126,29 @@ namespace Ryujinx.Modules } } - if (_buildUrl == null) + // If build not done, assume no new update are available. + if (_buildUrl is null) { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } + _running = false; + return; } } catch (Exception exception) { Logger.Error?.Print(LogClass.Application, exception.Message); - GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); + + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); + + _running = false; return; } @@ -160,8 +159,13 @@ namespace Ryujinx.Modules } catch { - GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; return; } @@ -170,11 +174,12 @@ namespace Ryujinx.Modules { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } - Running = false; - mainWindow.UpdateMenuItem.Sensitive = true; + _running = false; return; } @@ -197,13 +202,39 @@ namespace Ryujinx.Modules _buildSize = -1; } - // Show a message asking the user if they want to update - UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl); - updateDialog.Show(); + await Dispatcher.UIThread.InvokeAsync(async () => + { + // Show a message asking the user if they want to update + var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], + $"{Program.Version} -> {newVersion}"); + + if (shouldUpdate) + { + await UpdateRyujinx(mainWindow, _buildUrl); + } + else + { + _running = false; + } + }); + } + + private static HttpClient ConstructHttpClient() + { + HttpClient result = new(); + + // Required by GitHub to interact with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; } - public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) + private static async Task UpdateRyujinx(Window parent, string downloadUrl) { + _updateSuccessful = false; + // Empty update dir, although it shouldn't ever have anything inside it if (Directory.Exists(_updateDir)) { @@ -214,22 +245,93 @@ namespace Ryujinx.Modules string updateFile = Path.Combine(_updateDir, "update.bin"); - // Download the update .zip - updateDialog.MainText.Text = "Downloading Update..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = 100; + TaskDialog taskDialog = new() + { + Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], + IconSource = new SymbolIconSource { Symbol = Symbol.Download }, + ShowProgressBar = true, + XamlRoot = parent, + }; - if (_buildSize >= 0) + taskDialog.Opened += (s, e) => { - DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile); - } - else + if (_buildSize >= 0) + { + DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); + } + else + { + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + } + }; + + await taskDialog.ShowAsync(true); + + if (_updateSuccessful) { - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + bool shouldRestart = true; + + if (!OperatingSystem.IsMacOS()) + { + shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); + } + + if (shouldRestart) + { + List<string> arguments = CommandLineState.Arguments.ToList(); + string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; + + // On macOS we perform the update at relaunch. + if (OperatingSystem.IsMacOS()) + { + string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); + string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); + string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); + string currentPid = Environment.ProcessId.ToString(); + + arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); + Process.Start("/bin/bash", arguments); + } + else + { + // Find the process name. + string ryuName = Path.GetFileName(Environment.ProcessPath); + + // Some operating systems can see the renamed executable, so strip off the .ryuold if found. + if (ryuName.EndsWith(".ryuold")) + { + ryuName = ryuName[..^7]; + } + + // Fallback if the executable could not be found. + if (!Path.Exists(Path.Combine(executableDirectory, ryuName))) + { + ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + } + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = executableDirectory, + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + } + + Environment.Exit(0); + } } } - private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) { // Multi-Threaded Updater long chunkSize = _buildSize / ConnectionCount; @@ -253,6 +355,7 @@ namespace Ryujinx.Modules // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. using WebClient client = new(); #pragma warning restore SYSLIB0014 + webClients.Add(client); if (i == ConnectionCount - 1) @@ -272,7 +375,7 @@ namespace Ryujinx.Modules Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); - updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount; + taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); }; client.DownloadDataCompleted += (_, args) => @@ -283,6 +386,8 @@ namespace Ryujinx.Modules { webClients[index].Dispose(); + taskDialog.Hide(); + return; } @@ -300,18 +405,24 @@ namespace Ryujinx.Modules File.WriteAllBytes(updateFile, mergedFileBytes); + // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. + if (OperatingSystem.IsMacOS()) + { + using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }); + + xattrProcess.WaitForExit(); + } + try { - InstallUpdate(updateDialog, updateFile); + InstallUpdate(taskDialog, updateFile); } catch (Exception e) { Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); - - return; + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); } } }; @@ -330,14 +441,14 @@ namespace Ryujinx.Modules webClient.CancelAsync(); } - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); return; } } } - private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) { using HttpClient client = new(); // We do not want to timeout while downloading @@ -363,151 +474,165 @@ namespace Ryujinx.Modules byteWritten += readSize; - updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100; + taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); + updateFileStream.Write(buffer, 0, readSize); } - InstallUpdate(updateDialog, updateFile); + InstallUpdate(taskDialog, updateFile); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double GetPercentage(double value, double max) + { + return max == 0 ? 0 : value / max * 100; } - private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) { - Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile)) + Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)) { Name = "Updater.SingleThreadWorker", }; + worker.Start(); } - private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile) + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) { - // Extract Update - updateDialog.MainText.Text = "Extracting Update..."; - updateDialog.ProgressBar.Value = 0; + using Stream inStream = File.OpenRead(archivePath); + using GZipInputStream gzipStream = new(inStream); + using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - if (OperatingSystem.IsLinux()) - { - using Stream inStream = File.OpenRead(updateFile); - using Stream gzipStream = new GZipInputStream(inStream); - using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - updateDialog.ProgressBar.MaxValue = inStream.Length; + TarEntry tarEntry; - await Task.Run(() => + while ((tarEntry = tarStream.GetNextEntry()) is not null) + { + if (tarEntry.IsDirectory) { - TarEntry tarEntry; - - if (!OperatingSystem.IsWindows()) - { - while ((tarEntry = tarStream.GetNextEntry()) != null) - { - if (tarEntry.IsDirectory) - { - continue; - } - - string outPath = Path.Combine(_updateDir, tarEntry.Name); + continue; + } - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name); - using FileStream outStream = File.OpenWrite(outPath); - tarStream.CopyEntryContents(outStream); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); + using FileStream outStream = File.OpenWrite(outPath); + tarStream.CopyEntryContents(outStream); - TarEntry entry = tarEntry; + File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); - Application.Invoke(delegate - { - updateDialog.ProgressBar.Value += entry.Size; - }); - } + Dispatcher.UIThread.Post(() => + { + if (tarEntry is null) + { + return; } - }); - updateDialog.ProgressBar.Value = inStream.Length; + taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal); + }); } - else - { - using Stream inStream = File.OpenRead(updateFile); - using ZipFile zipFile = new(inStream); - updateDialog.ProgressBar.MaxValue = zipFile.Count; + } - await Task.Run(() => + private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) + { + using Stream inStream = File.OpenRead(archivePath); + using ZipFile zipFile = new(inStream); + + double count = 0; + foreach (ZipEntry zipEntry in zipFile) + { + count++; + if (zipEntry.IsDirectory) { - foreach (ZipEntry zipEntry in zipFile) - { - if (zipEntry.IsDirectory) - { - continue; - } + continue; + } - string outPath = Path.Combine(_updateDir, zipEntry.Name); + string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name); - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - using Stream zipStream = zipFile.GetInputStream(zipEntry); - using FileStream outStream = File.OpenWrite(outPath); - zipStream.CopyTo(outStream); + using Stream zipStream = zipFile.GetInputStream(zipEntry); + using FileStream outStream = File.OpenWrite(outPath); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + zipStream.CopyTo(outStream); - Application.Invoke(delegate - { - updateDialog.ProgressBar.Value++; - }); - } + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal); }); } + } + + private static void InstallUpdate(TaskDialog taskDialog, string updateFile) + { + // Extract Update + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + ExtractTarGzipFile(taskDialog, updateFile, _updateDir); + } + else if (OperatingSystem.IsWindows()) + { + ExtractZipFile(taskDialog, updateFile, _updateDir); + } + else + { + throw new NotSupportedException(); + } // Delete downloaded zip File.Delete(updateFile); List<string> allFiles = EnumerateFilesToDelete().ToList(); - updateDialog.MainText.Text = "Renaming Old Files..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = allFiles.Count; + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - // Replace old files - await Task.Run(() => + // NOTE: On macOS, replacement is delayed to the restart phase. + if (!OperatingSystem.IsMacOS()) { + // Replace old files + double count = 0; foreach (string file in allFiles) { + count++; try { File.Move(file, file + ".ryuold"); - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - updateDialog.ProgressBar.Value++; + taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); }); } catch { - Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file); + Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); } } - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - updateDialog.MainText.Text = "Adding New Files..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length; + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); }); - MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog); - }); + MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); - Directory.Delete(_updateDir, true); + Directory.Delete(_updateDir, true); + } - updateDialog.MainText.Text = "Update Complete!"; - updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?"; - updateDialog.Modal = true; + _updateSuccessful = true; - updateDialog.ProgressBar.Hide(); - updateDialog.YesButton.Show(); - updateDialog.NoButton.Show(); + taskDialog.Hide(); } public static bool CanUpdate(bool showWarnings) @@ -517,7 +642,11 @@ namespace Ryujinx.Modules { if (showWarnings) { - GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!"); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) + ); } return false; @@ -527,7 +656,11 @@ namespace Ryujinx.Modules { if (showWarnings) { - GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } return false; @@ -539,11 +672,19 @@ namespace Ryujinx.Modules { if (ReleaseInformation.IsFlatHubBuild) { - GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) + ); } else { - GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } } @@ -557,7 +698,7 @@ namespace Ryujinx.Modules var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. // Determine and exclude user files only when the updater is running, not when cleaning old files - if (Running) + if (_running && !OperatingSystem.IsMacOS()) { // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); @@ -583,8 +724,9 @@ namespace Ryujinx.Modules return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); } - private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog) + private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) { + int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; foreach (string directory in Directory.GetDirectories(root)) { string dirName = Path.GetFileName(directory); @@ -594,28 +736,28 @@ namespace Ryujinx.Modules Directory.CreateDirectory(Path.Combine(dest, dirName)); } - MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog); + MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); } + double count = 0; foreach (string file in Directory.GetFiles(root)) { + count++; + File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - dialog.ProgressBar.Value++; + taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); }); } } public static void CleanupUpdate() { - foreach (string file in EnumerateFilesToDelete()) + foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) { - if (Path.GetExtension(file).EndsWith(".ryuold")) - { - File.Delete(file); - } + File.Delete(file); } } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 1845c512..aecc585f 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -1,4 +1,7 @@ -using Gtk; +using Avalonia; +using Avalonia.Threading; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; @@ -6,139 +9,80 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; using Ryujinx.Modules; using Ryujinx.SDL2.Common; -using Ryujinx.UI; using Ryujinx.UI.Common; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.SystemInfo; -using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp.Formats.Jpeg; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; -namespace Ryujinx +namespace Ryujinx.Ava { - partial class Program + internal partial class Program { - public static double WindowScaleFactor { get; private set; } - + public static double WindowScaleFactor { get; set; } + public static double DesktopScaleFactor { get; set; } = 1.0; public static string Version { get; private set; } - - public static string ConfigurationPath { get; set; } - - public static string CommandLineProfile { get; set; } - - private const string X11LibraryName = "libX11"; - - [LibraryImport(X11LibraryName)] - private static partial int XInitThreads(); + public static string ConfigurationPath { get; private set; } + public static bool PreviewerDetached { get; private set; } [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - [LibraryImport("libc", SetLastError = true)] - private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); - - private const uint MbIconWarning = 0x30; + private const uint MbIconwarning = 0x30; - static Program() - { - if (OperatingSystem.IsLinux()) - { - NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) => - { - if (name != X11LibraryName) - { - return IntPtr.Zero; - } - - if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result)) - { - if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result)) - { - return IntPtr.Zero; - } - } - - return result; - }); - } - } - - static void Main(string[] args) + public static void Main(string[] args) { Version = ReleaseInformation.Version; if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { - MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); } - // Parse arguments - CommandLineState.ParseArguments(args); - - // Hook unhandled exception and process exit events. - GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); + PreviewerDetached = true; - // Make process DPI aware for proper window sizing on high-res screens. - ForceDpiAware.Windows(); - WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + Initialize(args); - // Delete backup files after updating. - Task.Run(Updater.CleanupUpdate); + LoggerAdapter.Register(); - Console.Title = $"Ryujinx Console {Version}"; - - // NOTE: GTK3 doesn't init X11 in a multi threaded way. - // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads). - if (OperatingSystem.IsLinux()) - { - if (XInitThreads() == 0) - { - throw new NotSupportedException("Failed to initialize multi-threading support."); - } - - Environment.SetEnvironmentVariable("GDK_BACKEND", "x11"); - setenv("GDK_BACKEND", "x11", 1); - } - - if (OperatingSystem.IsMacOS()) - { - string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); - string resourcesDataDir; + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + } - if (Path.GetFileName(baseDirectory) == "MacOS") + public static AppBuilder BuildAvaloniaApp() + { + return AppBuilder.Configure<App>() + .UsePlatformDetect() + .With(new X11PlatformOptions { - resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources"); - } - else + EnableMultiTouch = true, + EnableIme = true, + EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", + RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, + }) + .With(new Win32PlatformOptions { - resourcesDataDir = baseDirectory; - } - - static void SetEnvironmentVariableNoCaching(string key, string value) - { - int res = setenv(key, value, 1); - Debug.Assert(res != -1); - } + WinUICompositionBackdropCornerRadius = 8.0f, + RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, + }) + .UseSkia(); + } - // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. - SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + private static void Initialize(string[] args) + { + // Parse arguments + CommandLineState.ParseArguments(args); - // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. - SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + // Delete backup files after updating. + Task.Run(Updater.CleanupUpdate); - SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); - } + Console.Title = $"Ryujinx Console {Version}"; - string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); - Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + // Hook unhandled exception and process exit events. + AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit(); // Setup base data directory. AppDataManager.Initialize(CommandLineState.BaseDirPathArg); @@ -153,29 +97,47 @@ namespace Ryujinx DiscordIntegrationModule.Initialize(); // Initialize SDL2 driver - SDL2Driver.MainThreadDispatcher = action => + SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); + + ReloadConfig(); + + WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + + // Logging system information. + PrintSystemInfo(); + + // Enable OGL multithreading on the driver, when available. + DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); + + // Check if keys exists. + if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) { - Application.Invoke(delegate + if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) { - action(); - }); - }; + MainWindow.ShowKeyErrorOnLoad = true; + } + } - // Sets ImageSharp Jpeg Encoder Quality. - SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() + if (CommandLineState.LaunchPathArg != null) { - Quality = 100, - }); + MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); + } + } + public static void ReloadConfig() + { string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); // Now load the configuration as the other subsystems are now registered - ConfigurationPath = File.Exists(localConfigurationPath) - ? localConfigurationPath - : File.Exists(appDataConfigurationPath) - ? appDataConfigurationPath - : null; + if (File.Exists(localConfigurationPath)) + { + ConfigurationPath = localConfigurationPath; + } + else if (File.Exists(appDataConfigurationPath)) + { + ConfigurationPath = appDataConfigurationPath; + } if (ConfigurationPath == null) { @@ -199,7 +161,7 @@ namespace Ryujinx } } - // Check if graphics backend was overridden. + // Check if graphics backend was overridden if (CommandLineState.OverrideGraphicsBackend != null) { if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") @@ -212,6 +174,12 @@ namespace Ryujinx } } + // Check if docked mode was overriden. + if (CommandLineState.OverrideDockedMode.HasValue) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; + } + // Check if HideCursor was overridden. if (CommandLineState.OverrideHideCursor is not null) { @@ -223,114 +191,6 @@ namespace Ryujinx _ => ConfigurationState.Instance.HideCursor.Value, }; } - - // Check if docked mode was overridden. - if (CommandLineState.OverrideDockedMode.HasValue) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; - } - - // Logging system information. - PrintSystemInfo(); - - // Enable OGL multithreading on the driver, when available. - BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); - - // Initialize Gtk. - Application.Init(); - - // Check if keys exists. - bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); - bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")); - if (!hasSystemProdKeys && !hasCommonProdKeys) - { - UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); - } - - // Show the main window UI. - MainWindow mainWindow = new(); - mainWindow.Show(); - - if (OperatingSystem.IsLinux()) - { - int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; - - if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) - { - Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})"); - - if (LinuxHelper.PkExecPath is not null) - { - var buttonTexts = new Dictionary<int, string>() - { - { 0, "Yes, until the next restart" }, - { 1, "Yes, permanently" }, - { 2, "No" }, - }; - - ResponseType response = GtkDialog.CreateCustomDialog( - "Ryujinx - Low limit for memory mappings detected", - $"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?", - "Some games might try to create more memory mappings than currently allowed. " + - "Ryujinx will crash as soon as this limit gets exceeded.", - buttonTexts, - MessageType.Question); - - int rc; - - switch ((int)response) - { - case 0: - rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); - } - break; - case 1: - rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); - } - break; - } - } - else - { - GtkDialog.CreateWarningDialog( - "Max amount of memory mappings is lower than recommended.", - $"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." + - "Some games might try to create more memory mappings than currently allowed. " + - "Ryujinx will crash as soon as this limit gets exceeded.\n\n" + - "You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that."); - } - } - } - - if (CommandLineState.LaunchPathArg != null) - { - mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); - } - - if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) - { - Updater.BeginParse(mainWindow, false).ContinueWith(task => - { - Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); - }, TaskContinuationOptions.OnlyOnFaulted); - } - - Application.Run(); } private static void PrintSystemInfo() @@ -338,8 +198,7 @@ namespace Ryujinx Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); SystemInfo.Gather().Print(); - var enabledLogs = Logger.GetEnabledLevels(); - Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}"); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}"); if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) { diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 68bf9898..b3d312f6 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -1,5 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> - +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> @@ -7,11 +6,17 @@ <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Version>1.0.0-dirty</Version> <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> - <!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. --> - <SkipGtkInstall>true</SkipGtkInstall> + <SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate> + <ApplicationIcon>Ryujinx.ico</ApplicationIcon> <TieredPGO>true</TieredPGO> + <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> + <ApplicationManifest>app.manifest</ApplicationManifest> </PropertyGroup> + <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))"> + <Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" /> + </Target> + <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''"> <PublishSingleFile>true</PublishSingleFile> <TrimmerSingleWarn>false</TrimmerSingleWarn> @@ -19,33 +24,56 @@ <TrimMode>partial</TrimMode> </PropertyGroup> + <!-- + FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json. + See: + https://github.com/amwx/FluentAvalonia/issues/481 + https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/ + --> + <PropertyGroup> + <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> + </PropertyGroup> + <ItemGroup> - <PackageReference Include="Ryujinx.GtkSharp" /> - <PackageReference Include="GtkSharp.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> - <PackageReference Include="GtkSharp.Dependencies.osx" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" /> - <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> + <PackageReference Include="Avalonia" /> + <PackageReference Include="Avalonia.Desktop" /> + <PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" /> + <PackageReference Include="Avalonia.Controls.DataGrid" /> + <PackageReference Include="Avalonia.Markup.Xaml.Loader" /> + <PackageReference Include="Avalonia.Svg" /> + <PackageReference Include="Avalonia.Svg.Skia" /> + <PackageReference Include="DynamicData" /> + <PackageReference Include="FluentAvaloniaUI" /> + + <PackageReference Include="OpenTK.Core" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> + <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" /> - <PackageReference Include="OpenTK.Core" /> - <PackageReference Include="OpenTK.Graphics" /> + <PackageReference Include="Silk.NET.Vulkan" /> + <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" /> + <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> <PackageReference Include="SPB" /> <PackageReference Include="SharpZipLib" /> <PackageReference Include="SixLabors.ImageSharp" /> + + <!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA --> + <PackageReference Include="System.Drawing.Common" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" /> + <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> <ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" /> - <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" /> <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> - <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" /> <ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" /> + <ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> <ItemGroup> @@ -73,32 +101,66 @@ </Content> </ItemGroup> - <!-- Due to .net core 3.1 embedded resource loading --> - <PropertyGroup> - <EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention> - <ApplicationIcon>Ryujinx.ico</ApplicationIcon> - </PropertyGroup> - <ItemGroup> - <None Remove="UI\MainWindow.glade" /> - <None Remove="UI\Widgets\ProfileDialog.glade" /> - <None Remove="UI\Windows\CheatWindow.glade" /> - <None Remove="UI\Windows\ControllerWindow.glade" /> - <None Remove="UI\Windows\DlcWindow.glade" /> - <None Remove="UI\Windows\SettingsWindow.glade" /> - <None Remove="UI\Windows\TitleUpdateWindow.glade" /> - <None Remove="Modules\Updater\UpdateDialog.glade" /> + <AvaloniaResource Include="UI\**\*.xaml"> + <SubType>Designer</SubType> + </AvaloniaResource> + <AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" /> + <AvaloniaResource Include="Assets\Styles\Themes.xaml"> + <Generator>MSBuild:Compile</Generator> + </AvaloniaResource> + <AvaloniaResource Include="Assets\Styles\Styles.xaml" /> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="UI\MainWindow.glade" /> - <EmbeddedResource Include="UI\Widgets\ProfileDialog.glade" /> - <EmbeddedResource Include="UI\Windows\CheatWindow.glade" /> - <EmbeddedResource Include="UI\Windows\ControllerWindow.glade" /> - <EmbeddedResource Include="UI\Windows\DlcWindow.glade" /> - <EmbeddedResource Include="UI\Windows\SettingsWindow.glade" /> - <EmbeddedResource Include="UI\Windows\TitleUpdateWindow.glade" /> - <EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" /> + <None Remove="Assets\Locales\el_GR.json" /> + <None Remove="Assets\Locales\en_US.json" /> + <None Remove="Assets\Locales\es_ES.json" /> + <None Remove="Assets\Locales\fr_FR.json" /> + <None Remove="Assets\Locales\he_IL.json" /> + <None Remove="Assets\Locales\de_DE.json" /> + <None Remove="Assets\Locales\it_IT.json" /> + <None Remove="Assets\Locales\ja_JP.json" /> + <None Remove="Assets\Locales\ko_KR.json" /> + <None Remove="Assets\Locales\pl_PL.json" /> + <None Remove="Assets\Locales\pt_BR.json" /> + <None Remove="Assets\Locales\ru_RU.json" /> + <None Remove="Assets\Locales\tr_TR.json" /> + <None Remove="Assets\Locales\uk_UA.json" /> + <None Remove="Assets\Locales\zh_CN.json" /> + <None Remove="Assets\Locales\zh_TW.json" /> + <None Remove="Assets\Styles\Styles.xaml" /> + <None Remove="Assets\Styles\Themes.xaml" /> + <None Remove="Assets\Icons\Controller_JoyConLeft.svg" /> + <None Remove="Assets\Icons\Controller_JoyConPair.svg" /> + <None Remove="Assets\Icons\Controller_JoyConRight.svg" /> + <None Remove="Assets\Icons\Controller_ProCon.svg" /> </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Assets\Locales\el_GR.json" /> + <EmbeddedResource Include="Assets\Locales\en_US.json" /> + <EmbeddedResource Include="Assets\Locales\es_ES.json" /> + <EmbeddedResource Include="Assets\Locales\fr_FR.json" /> + <EmbeddedResource Include="Assets\Locales\he_IL.json" /> + <EmbeddedResource Include="Assets\Locales\de_DE.json" /> + <EmbeddedResource Include="Assets\Locales\it_IT.json" /> + <EmbeddedResource Include="Assets\Locales\ja_JP.json" /> + <EmbeddedResource Include="Assets\Locales\ko_KR.json" /> + <EmbeddedResource Include="Assets\Locales\pl_PL.json" /> + <EmbeddedResource Include="Assets\Locales\pt_BR.json" /> + <EmbeddedResource Include="Assets\Locales\ru_RU.json" /> + <EmbeddedResource Include="Assets\Locales\tr_TR.json" /> + <EmbeddedResource Include="Assets\Locales\uk_UA.json" /> + <EmbeddedResource Include="Assets\Locales\zh_CN.json" /> + <EmbeddedResource Include="Assets\Locales\zh_TW.json" /> + <EmbeddedResource Include="Assets\Styles\Styles.xaml" /> + <EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" /> + <EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" /> + <EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" /> + <EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" /> + </ItemGroup> + <ItemGroup> + <AdditionalFiles Include="Assets\Locales\en_US.json" /> + </ItemGroup> </Project> diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs new file mode 100644 index 00000000..4bcc35a7 --- /dev/null +++ b/src/Ryujinx/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/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs new file mode 100644 index 00000000..531d0061 --- /dev/null +++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -0,0 +1,162 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Threading; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.UI; +using System; +using System.Threading; +using HidKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Applet +{ + class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler + { + private MainWindow _parent; + private readonly OffscreenTextBox _hiddenTextBox; + private bool _canProcessInput; + private IDisposable _textChangedSubscription; + private IDisposable _selectionStartChangedSubscription; + private IDisposable _selectionEndtextChangedSubscription; + + public AvaloniaDynamicTextInputHandler(MainWindow parent) + { + _parent = parent; + + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease += AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput += AvaloniaDynamicTextInputHandler_TextInput; + + _hiddenTextBox = _parent.HiddenTextBox; + + Dispatcher.UIThread.Post(() => + { + _textChangedSubscription = _hiddenTextBox.GetObservable(TextBox.TextProperty).Subscribe(TextChanged); + _selectionStartChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionStartProperty).Subscribe(SelectionChanged); + _selectionEndtextChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionEndProperty).Subscribe(SelectionChanged); + }); + } + + private void TextChanged(string text) + { + TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void SelectionChanged(int selection) + { + if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) + { + _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd; + } + + TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendText(text); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyUpEvent(e); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = OffscreenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyDownEvent(e); + } + }); + } + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public void Dispose() + { + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput -= AvaloniaDynamicTextInputHandler_TextInput; + + _textChangedSubscription?.Dispose(); + _selectionStartChangedSubscription?.Dispose(); + _selectionEndtextChangedSubscription?.Dispose(); + + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Clear(); + _parent.ViewModel.RendererHostControl.Focus(); + + _parent = null; + }); + } + + public void SetText(string text, int cursorBegin) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.CaretIndex = cursorBegin; + }); + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.SelectionStart = cursorBegin; + _hiddenTextBox.SelectionEnd = cursorEnd; + }); + } + } +} diff --git a/src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs b/src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs new file mode 100644 index 00000000..016fb484 --- /dev/null +++ b/src/Ryujinx/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/UI/Applet/ControllerAppletDialog.axaml b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml new file mode 100644 index 00000000..b2c22f6b --- /dev/null +++ b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml @@ -0,0 +1,145 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Applet.ControllerAppletDialog" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:applet="using:Ryujinx.Ava.UI.Applet" + mc:Ignorable="d" + Width="400" + Focusable="True" + x:DataType="applet:ControllerAppletDialog"> + <Grid + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <Border + Grid.Column="0" + Grid.Row="0" + Grid.ColumnSpan="2" + Margin="0 0 0 10" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5"> + <StackPanel + Spacing="10" + Margin="10"> + <TextBlock + Text="{locale:Locale ControllerAppletDescription}" /> + <TextBlock + IsVisible="{Binding IsDocked}" + FontWeight="Bold" + Text="{locale:Locale ControllerAppletDocked}" /> + </StackPanel> + </Border> + <Border + Grid.Column="0" + Grid.Row="1" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + Margin="0 0 10 0"> + <StackPanel + Margin="10" + Spacing="10" + Orientation="Vertical"> + <TextBlock + HorizontalAlignment="Center" + VerticalAlignment="Center" + TextAlignment="Center" + FontWeight="Bold" + Text="{locale:Locale ControllerAppletControllers}" /> + <StackPanel + Spacing="10" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <Image + Height="50" + Width="50" + Stretch="Uniform" + Source="{Binding ProControllerImage}" + IsVisible="{Binding SupportsProController}" /> + <Image + Height="50" + Width="50" + Stretch="Uniform" + Source="{Binding JoyconPairImage}" + IsVisible="{Binding SupportsJoyconPair}" /> + <Image + Height="50" + Width="50" + Stretch="Uniform" + Source="{Binding JoyconLeftImage}" + IsVisible="{Binding SupportsLeftJoycon}" /> + <Image + Height="50" + Width="50" + Stretch="Uniform" + Source="{Binding JoyconRightImage}" + IsVisible="{Binding SupportsRightJoycon}" /> + </StackPanel> + </StackPanel> + </Border> + <Border + Grid.Column="1" + Grid.Row="1" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5"> + <StackPanel + Margin="10" + Spacing="10" + Orientation="Vertical"> + <TextBlock + HorizontalAlignment="Center" + VerticalAlignment="Center" + TextAlignment="Center" + FontWeight="Bold" + Text="{locale:Locale ControllerAppletPlayers}" /> + <Border Height="50"> + <TextBlock + HorizontalAlignment="Center" + VerticalAlignment="Center" + TextAlignment="Center" + FontSize="40" + FontWeight="Thin" + Text="{Binding PlayerCount}" /> + </Border> + </StackPanel> + </Border> + <Panel + Margin="0 24 0 0" + Grid.Column="0" + Grid.Row="2" + Grid.ColumnSpan="2"> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Command="{Binding OpenSettingsWindow}"> + <TextBlock Text="{locale:Locale DialogOpenSettingsWindowLabel}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Command="{Binding Close}"> + <TextBlock Text="{locale:Locale SettingsButtonClose}" /> + </Button> + </StackPanel> + </Panel> + </Grid> +</UserControl> + diff --git a/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs new file mode 100644 index 00000000..5a98b164 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs @@ -0,0 +1,140 @@ +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Hid; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ControllerAppletDialog : UserControl + { + private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg"; + + public static SvgImage ProControllerImage => GetResource(ProControllerResource); + public static SvgImage JoyconPairImage => GetResource(JoyConPairResource); + public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource); + public static SvgImage JoyconRightImage => GetResource(JoyConRightResource); + + public string PlayerCount { get; set; } = ""; + public bool SupportsProController { get; set; } + public bool SupportsLeftJoycon { get; set; } + public bool SupportsRightJoycon { get; set; } + public bool SupportsJoyconPair { get; set; } + public bool IsDocked { get; set; } + + private readonly MainWindow _mainWindow; + + public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args) + { + if (args.PlayerCountMin == args.PlayerCountMax) + { + PlayerCount = args.PlayerCountMin.ToString(); + } + else + { + PlayerCount = $"{args.PlayerCountMin} - {args.PlayerCountMax}"; + } + + SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0; + SupportsLeftJoycon = (args.SupportedStyles & ControllerType.JoyconLeft) != 0; + SupportsRightJoycon = (args.SupportedStyles & ControllerType.JoyconRight) != 0; + SupportsJoyconPair = (args.SupportedStyles & ControllerType.JoyconPair) != 0; + + IsDocked = args.IsDocked; + + _mainWindow = mainWindow; + + DataContext = this; + + InitializeComponent(); + } + + public ControllerAppletDialog(MainWindow mainWindow) + { + _mainWindow = mainWindow; + DataContext = this; + + InitializeComponent(); + } + + public static async Task<UserResult> ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args) + { + ContentDialog contentDialog = new(); + UserResult result = UserResult.Cancel; + ControllerAppletDialog content = new(window, args); + + contentDialog.Title = LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle]; + contentDialog.Content = content; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + } + } + + contentDialog.Closed += Handler; + + Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await ContentDialogHelper.ShowAsync(contentDialog); + + return result; + } + + private static SvgImage GetResource(string path) + { + SvgImage image = new(); + + if (!string.IsNullOrWhiteSpace(path)) + { + SvgSource source = new(default(Uri)); + + source.Load(EmbeddedResources.GetStream(path)); + + image.Source = source; + } + + return image; + } + + public void OpenSettingsWindow() + { + if (_mainWindow.SettingsWindow == null) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + _mainWindow.SettingsWindow = new SettingsWindow(_mainWindow.VirtualFileSystem, _mainWindow.ContentManager); + _mainWindow.SettingsWindow.NavPanel.Content = _mainWindow.SettingsWindow.InputPage; + _mainWindow.SettingsWindow.NavPanel.SelectedItem = _mainWindow.SettingsWindow.NavPanel.MenuItems.ElementAt(1); + + await ContentDialogHelper.ShowWindowAsync(_mainWindow.SettingsWindow, _mainWindow); + _mainWindow.SettingsWindow = null; + this.Close(); + }); + } + } + + public void Close() + { + ((ContentDialog)Parent)?.Hide(); + } + } +} + diff --git a/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs b/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs deleted file mode 100644 index 7f8cc0e9..00000000 --- a/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Gtk; -using Ryujinx.UI.Common.Configuration; -using System.Reflection; - -namespace Ryujinx.UI.Applet -{ - internal class ErrorAppletDialog : MessageDialog - { - public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) - { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - int responseId = 0; - - if (buttons != null) - { - foreach (string buttonText in buttons) - { - AddButton(buttonText, responseId); - responseId++; - } - } - else - { - AddButton("OK", 0); - } - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml new file mode 100644 index 00000000..51f37051 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml @@ -0,0 +1,54 @@ +<Window + x:Class="Ryujinx.Ava.UI.Applet.ErrorAppletWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="{locale:Locale ErrorWindowTitle}" + xmlns:views="using:Ryujinx.Ava.UI.Applet" + Width="450" + Height="340" + CanResize="False" + x:DataType="views:ErrorAppletWindow" + SizeToContent="Height" + mc:Ignorable="d" + Focusable="True"> + <Grid + Margin="20" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Image + Grid.Row="1" + Grid.RowSpan="2" + Grid.Column="0" + Height="80" + MinWidth="50" + Margin="5,10,20,10" + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> + <TextBlock + Grid.Row="1" + Grid.Column="1" + Margin="10" + VerticalAlignment="Stretch" + Text="{Binding Message}" + TextWrapping="Wrap" /> + <StackPanel + Name="ButtonStack" + Grid.Row="2" + Grid.Column="1" + Margin="10" + HorizontalAlignment="Right" + Orientation="Horizontal" + Spacing="10" /> + </Grid> +</Window> diff --git a/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs new file mode 100644 index 00000000..ec6f7682 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs @@ -0,0 +1,74 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ErrorAppletWindow : StyleableWindow + { + private readonly Window _owner; + private object _buttonResponse; + + public ErrorAppletWindow(Window owner, string[] buttons, string message) + { + _owner = owner; + Message = message; + DataContext = this; + InitializeComponent(); + + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton(LocaleManager.Instance[LocaleKeys.InputDialogOk], 0); + } + } + + public ErrorAppletWindow() + { + DataContext = this; + InitializeComponent(); + } + + public string Message { get; set; } + + private void AddButton(string label, object tag) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Button button = new() { Content = label, Tag = tag }; + + button.Click += Button_Click; + ButtonStack.Children.Add(button); + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + _buttonResponse = button.Tag; + } + + Close(); + } + + public async Task<object> Run() + { + await ShowDialog(_owner); + + return _buttonResponse; + } + } +} diff --git a/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs deleted file mode 100644 index 0e560b78..00000000 --- a/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Gtk; -using Ryujinx.HLE.UI; -using Ryujinx.Input.GTK3; -using Ryujinx.UI.Widgets; -using System.Threading; - -namespace Ryujinx.UI.Applet -{ - /// <summary> - /// Class that forwards key events to a GTK Entry so they can be processed into text. - /// </summary> - internal class GtkDynamicTextInputHandler : IDynamicTextInputHandler - { - private readonly Window _parent; - private readonly OffscreenWindow _inputToTextWindow = new(); - private readonly RawInputToTextEntry _inputToTextEntry = new(); - - private bool _canProcessInput; - - public event DynamicTextChangedHandler TextChangedEvent; - public event KeyPressedHandler KeyPressedEvent; - public event KeyReleasedHandler KeyReleasedEvent; - - public bool TextProcessingEnabled - { - get - { - return Volatile.Read(ref _canProcessInput); - } - - set - { - Volatile.Write(ref _canProcessInput, value); - } - } - - public GtkDynamicTextInputHandler(Window parent) - { - _parent = parent; - _parent.KeyPressEvent += HandleKeyPressEvent; - _parent.KeyReleaseEvent += HandleKeyReleaseEvent; - - _inputToTextWindow.Add(_inputToTextEntry); - - _inputToTextEntry.TruncateMultiline = true; - - // Start with input processing turned off so the text box won't accumulate text - // if the user is playing on the keyboard. - _canProcessInput = false; - } - - [GLib.ConnectBefore()] - private void HandleKeyPressEvent(object o, KeyPressEventArgs args) - { - var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); - - if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) - { - return; - } - - if (_canProcessInput) - { - _inputToTextEntry.SendKeyPressEvent(o, args); - _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); - TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); - } - } - - [GLib.ConnectBefore()] - private void HandleKeyReleaseEvent(object o, KeyReleaseEventArgs args) - { - var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); - - if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) - { - return; - } - - if (_canProcessInput) - { - // TODO (caian): This solution may have problems if the pause is sent after a key press - // and before a key release. But for now GTK Entry does not seem to use release events. - _inputToTextEntry.SendKeyReleaseEvent(o, args); - _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); - TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); - } - } - - public void SetText(string text, int cursorBegin) - { - _inputToTextEntry.Text = text; - _inputToTextEntry.Position = cursorBegin; - } - - public void SetText(string text, int cursorBegin, int cursorEnd) - { - _inputToTextEntry.Text = text; - _inputToTextEntry.SelectRegion(cursorBegin, cursorEnd); - } - - public void Dispose() - { - _parent.KeyPressEvent -= HandleKeyPressEvent; - _parent.KeyReleaseEvent -= HandleKeyReleaseEvent; - } - } -} diff --git a/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs deleted file mode 100644 index 1d918d21..00000000 --- a/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Gtk; -using Ryujinx.HLE.HOS.Applets; -using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; -using Ryujinx.HLE.UI; -using Ryujinx.UI.Widgets; -using System; -using System.Threading; - -namespace Ryujinx.UI.Applet -{ - internal class GtkHostUIHandler : IHostUIHandler - { - private readonly Window _parent; - - public IHostUITheme HostUITheme { get; } - - public GtkHostUIHandler(Window parent) - { - _parent = parent; - - HostUITheme = new GtkHostUITheme(parent); - } - - public bool DisplayMessageDialog(ControllerAppletUIArgs args) - { - string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; - - string message = $"Application requests <b>{playerCount}</b> player(s) with:\n\n" - + $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n" - + $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n" - + (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "") - + "<i>Please reconfigure Input now and then press OK.</i>"; - - return DisplayMessageDialog("Controller Applet", message); - } - - public bool DisplayMessageDialog(string title, string message) - { - ManualResetEvent dialogCloseEvent = new(false); - - bool okPressed = false; - - Application.Invoke(delegate - { - MessageDialog msgDialog = null; - - try - { - msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) - { - Title = title, - Text = message, - UseMarkup = true, - }; - - msgDialog.SetDefaultSize(400, 0); - - msgDialog.Response += (object o, ResponseArgs args) => - { - if (args.ResponseId == ResponseType.Ok) - { - okPressed = true; - } - - dialogCloseEvent.Set(); - msgDialog?.Dispose(); - }; - - msgDialog.Show(); - } - catch (Exception ex) - { - GtkDialog.CreateErrorDialog($"Error displaying Message Dialog: {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 ?? ""; - - Application.Invoke(delegate - { - try - { - var swkbdDialog = new SwkbdAppletDialog(_parent) - { - Title = "Software Keyboard", - Text = args.HeaderText, - SecondaryText = args.SubtitleText, - }; - - swkbdDialog.InputEntry.Text = inputText; - swkbdDialog.InputEntry.PlaceholderText = args.GuideText; - swkbdDialog.OkButton.Label = args.SubmitText; - - swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); - swkbdDialog.SetInputValidation(args.KeyboardMode); - - if (swkbdDialog.Run() == (int)ResponseType.Ok) - { - inputText = swkbdDialog.InputEntry.Text; - okPressed = true; - } - - swkbdDialog.Dispose(); - } - catch (Exception ex) - { - error = true; - - GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}"); - } - finally - { - dialogCloseEvent.Set(); - } - }); - - dialogCloseEvent.WaitOne(); - - userText = error ? null : inputText; - - return error || okPressed; - } - - public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value) - { - device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); - ((MainWindow)_parent).RendererWidget?.Exit(); - } - - public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) - { - ManualResetEvent dialogCloseEvent = new(false); - - bool showDetails = false; - - Application.Invoke(delegate - { - try - { - ErrorAppletDialog msgDialog = new(_parent, DialogFlags.DestroyWithParent, MessageType.Error, buttons) - { - Title = title, - Text = message, - UseMarkup = true, - WindowPosition = WindowPosition.CenterAlways, - }; - - msgDialog.SetDefaultSize(400, 0); - - msgDialog.Response += (object o, ResponseArgs args) => - { - if (buttons != null) - { - if (buttons.Length > 1) - { - if (args.ResponseId != (ResponseType)(buttons.Length - 1)) - { - showDetails = true; - } - } - } - - dialogCloseEvent.Set(); - msgDialog?.Dispose(); - }; - - msgDialog.Show(); - } - catch (Exception ex) - { - GtkDialog.CreateErrorDialog($"Error displaying ErrorApplet Dialog: {ex}"); - - dialogCloseEvent.Set(); - } - }); - - dialogCloseEvent.WaitOne(); - - return showDetails; - } - - public IDynamicTextInputHandler CreateDynamicTextInputHandler() - { - return new GtkDynamicTextInputHandler(_parent); - } - } -} diff --git a/src/Ryujinx/UI/Applet/GtkHostUITheme.cs b/src/Ryujinx/UI/Applet/GtkHostUITheme.cs deleted file mode 100644 index 52d1123b..00000000 --- a/src/Ryujinx/UI/Applet/GtkHostUITheme.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Gtk; -using Ryujinx.HLE.UI; -using System.Diagnostics; - -namespace Ryujinx.UI.Applet -{ - internal class GtkHostUITheme : IHostUITheme - { - private const int RenderSurfaceWidth = 32; - private const int RenderSurfaceHeight = 32; - - public string FontFamily { get; private set; } - - public ThemeColor DefaultBackgroundColor { get; } - public ThemeColor DefaultForegroundColor { get; } - public ThemeColor DefaultBorderColor { get; } - public ThemeColor SelectionBackgroundColor { get; } - public ThemeColor SelectionForegroundColor { get; } - - public GtkHostUITheme(Window parent) - { - Entry entry = new(); - entry.SetStateFlags(StateFlags.Selected, true); - - // Get the font and some colors directly from GTK. - FontFamily = entry.PangoContext.FontDescription.Family; - - // Get foreground colors from the style context. - - var defaultForegroundColor = entry.StyleContext.GetColor(StateFlags.Normal); - var selectedForegroundColor = entry.StyleContext.GetColor(StateFlags.Selected); - - DefaultForegroundColor = new ThemeColor((float)defaultForegroundColor.Alpha, (float)defaultForegroundColor.Red, (float)defaultForegroundColor.Green, (float)defaultForegroundColor.Blue); - SelectionForegroundColor = new ThemeColor((float)selectedForegroundColor.Alpha, (float)selectedForegroundColor.Red, (float)selectedForegroundColor.Green, (float)selectedForegroundColor.Blue); - - ListBoxRow row = new(); - row.SetStateFlags(StateFlags.Selected, true); - - // Request the main thread to render some UI elements to an image to get an approximation for the color. - // NOTE (caian): This will only take the color of the top-left corner of the background, which may be incorrect - // if someone provides a custom style with a gradient or image. - - using (var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, RenderSurfaceWidth, RenderSurfaceHeight)) - using (var context = new Cairo.Context(surface)) - { - context.SetSourceRGBA(1, 1, 1, 1); - context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); - context.Fill(); - - // The background color must be from the main Window because entry uses a different color. - parent.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); - - DefaultBackgroundColor = ToThemeColor(surface.Data); - - context.SetSourceRGBA(1, 1, 1, 1); - context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); - context.Fill(); - - // Use the background color of the list box row when selected as the text box frame color because they are the - // same in the default theme. - row.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); - - DefaultBorderColor = ToThemeColor(surface.Data); - } - - // Use the border color as the text selection color. - SelectionBackgroundColor = DefaultBorderColor; - } - - private static ThemeColor ToThemeColor(byte[] data) - { - Debug.Assert(data.Length == 4 * RenderSurfaceWidth * RenderSurfaceHeight); - - // Take the center-bottom pixel of the surface. - int position = 4 * (RenderSurfaceWidth * (RenderSurfaceHeight - 1) + RenderSurfaceWidth / 2); - - if (position + 4 > data.Length) - { - return new ThemeColor(1, 0, 0, 0); - } - - float a = data[position + 3] / 255.0f; - float r = data[position + 2] / 255.0f; - float g = data[position + 1] / 255.0f; - float b = data[position + 0] / 255.0f; - - return new ThemeColor(a, r, g, b); - } - } -} diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml new file mode 100644 index 00000000..b1c84734 --- /dev/null +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml @@ -0,0 +1,67 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Controls.SwkbdAppletDialog" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:views="using:Ryujinx.Ava.UI.Controls" + Width="400" + x:DataType="views:SwkbdAppletDialog" + mc:Ignorable="d" + Focusable="True"> + <Grid + Margin="20" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Image + Grid.Row="1" + Grid.RowSpan="5" + Height="80" + MinWidth="50" + Margin="5,10,20,10" + VerticalAlignment="Center" + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> + <TextBlock + Grid.Row="1" + Grid.Column="1" + Margin="5" + Text="{Binding MainText}" + TextWrapping="Wrap" /> + <TextBlock + Grid.Row="2" + Grid.Column="1" + Margin="5" + Text="{Binding SecondaryText}" + TextWrapping="Wrap" /> + <TextBox + Name="Input" + Grid.Row="3" + Grid.Column="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + Focusable="True" + KeyUp="Message_KeyUp" + Text="{Binding Message}" + TextInput="Message_TextInput" + TextWrapping="Wrap" + UseFloatingWatermark="True" /> + <TextBlock + Name="Error" + Grid.Row="4" + Grid.Column="1" + Margin="5" + HorizontalAlignment="Stretch" + TextWrapping="Wrap" /> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs new file mode 100644 index 00000000..af3837e4 --- /dev/null +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -0,0 +1,183 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + internal partial class SwkbdAppletDialog : UserControl + { + private Predicate<int> _checkLength = _ => true; + private Predicate<string> _checkInput = _ => true; + private int _inputMax; + private int _inputMin; + private readonly string _placeholder; + + private ContentDialog _host; + + public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message) + { + MainText = mainText; + SecondaryText = secondaryText; + Message = message ?? ""; + DataContext = this; + _placeholder = placeholder; + InitializeComponent(); + + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + } + + public SwkbdAppletDialog() + { + DataContext = this; + InitializeComponent(); + } + + protected override void OnGotFocus(GotFocusEventArgs e) + { + // FIXME: This does not work. Might be a bug in Avalonia with DialogHost + // Currently focus will be redirected to the overlay window instead. + Input.Focus(); + } + + public string Message { get; set; } = ""; + public string MainText { get; set; } = ""; + public string SecondaryText { get; set; } = ""; + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args) + { + ContentDialog contentDialog = new(); + + UserResult result = UserResult.Cancel; + + SwkbdAppletDialog content = new(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText); + + string input = string.Empty; + + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + content.SetInputValidation(args.KeyboardMode); + + content._host = contentDialog; + contentDialog.Title = title; + contentDialog.PrimaryButtonText = args.SubmitText; + contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); + contentDialog.SecondaryButtonText = ""; + contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel]; + contentDialog.Content = content; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + input = content.Input.Text; + } + } + + contentDialog.Closed += Handler; + + await ContentDialogHelper.ShowAsync(contentDialog); + + return (result, input); + } + + private void ApplyValidationInfo(string text) + { + Error.IsVisible = !string.IsNullOrEmpty(text); + Error.Text = text; + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + Error.IsVisible = false; + Error.FontStyle = FontStyle.Italic; + + string validationInfoText = ""; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + Error.IsVisible = false; + + _checkLength = length => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin); + + _checkLength = length => _inputMin <= length; + } + else + { + validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax); + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + ApplyValidationInfo(validationInfoText); + Message_TextInput(this, new TextInputEventArgs()); + } + + private void SetInputValidation(KeyboardMode mode) + { + string validationInfoText = Error.Text; + string localeText; + switch (mode) + { + case KeyboardMode.Numeric: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); + break; + case KeyboardMode.Alphabet: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); + break; + case KeyboardMode.ASCII: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(char.IsAscii); + break; + default: + _checkInput = _ => true; + break; + } + + ApplyValidationInfo(validationInfoText); + Message_TextInput(this, new TextInputEventArgs()); + } + + private void Message_TextInput(object sender, TextInputEventArgs e) + { + if (_host != null) + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); + } + } + + private void Message_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && _host.IsPrimaryButtonEnabled) + { + _host.Hide(ContentDialogResult.Primary); + } + else + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); + } + } + } +} diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs deleted file mode 100644 index 8045da91..00000000 --- a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Gtk; -using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; -using System; -using System.Linq; - -namespace Ryujinx.UI.Applet -{ - public class SwkbdAppletDialog : MessageDialog - { - private int _inputMin; - private int _inputMax; -#pragma warning disable IDE0052 // Remove unread private member - private KeyboardMode _mode; -#pragma warning restore IDE0052 - - private string _validationInfoText = ""; - - private Predicate<int> _checkLength = _ => true; - private Predicate<string> _checkInput = _ => true; - - private readonly Label _validationInfo; - - public Entry InputEntry { get; } - public Button OkButton { get; } - public Button CancelButton { get; } - - public SwkbdAppletDialog(Window parent) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null) - { - SetDefaultSize(300, 0); - - _validationInfo = new Label() - { - Visible = false, - }; - - InputEntry = new Entry() - { - Visible = true, - }; - - InputEntry.Activated += OnInputActivated; - InputEntry.Changed += OnInputChanged; - - OkButton = (Button)AddButton("OK", ResponseType.Ok); - CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel); - - ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0); - ((Box)MessageArea).PackEnd(InputEntry, true, true, 4); - } - - private void ApplyValidationInfo() - { - _validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText); - _validationInfo.Markup = _validationInfoText; - } - - public void SetInputLengthValidation(int min, int max) - { - _inputMin = Math.Min(min, max); - _inputMax = Math.Max(min, max); - - _validationInfo.Visible = false; - - if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. - { - _validationInfo.Visible = false; - - _checkLength = _ => true; - } - else if (_inputMin > 0 && _inputMax == int.MaxValue) - { - _validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> "; - - _checkLength = length => _inputMin <= length; - } - else - { - _validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> "; - - _checkLength = length => _inputMin <= length && length <= _inputMax; - } - - ApplyValidationInfo(); - OnInputChanged(this, EventArgs.Empty); - } - - public void SetInputValidation(KeyboardMode mode) - { - _mode = mode; - - switch (mode) - { - case KeyboardMode.Numeric: - _validationInfoText += "<i>Must be 0-9 or '.' only.</i>"; - _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); - break; - case KeyboardMode.Alphabet: - _validationInfoText += "<i>Must be non CJK-characters only.</i>"; - _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); - break; - case KeyboardMode.ASCII: - _validationInfoText += "<i>Must be ASCII text only.</i>"; - _checkInput = text => text.All(char.IsAscii); - break; - default: - _checkInput = _ => true; - break; - } - - ApplyValidationInfo(); - OnInputChanged(this, EventArgs.Empty); - } - - private void OnInputActivated(object sender, EventArgs e) - { - if (OkButton.IsSensitive) - { - Respond(ResponseType.Ok); - } - } - - private void OnInputChanged(object sender, EventArgs e) - { - OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text); - } - } -} diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml new file mode 100644 index 00000000..dd0926fc --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -0,0 +1,95 @@ +<MenuFlyout + x:Class="Ryujinx.Ava.UI.Controls.ApplicationContextMenu" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + x:DataType="viewModels:MainWindowViewModel"> + <MenuItem + Click="RunApplication_Click" + Header="{locale:Locale GameListContextMenuRunApplication}" /> + <MenuItem + Click="ToggleFavorite_Click" + Header="{locale:Locale GameListContextMenuToggleFavorite}" + ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> + <MenuItem + Click="CreateApplicationShortcut_Click" + Header="{locale:Locale GameListContextMenuCreateShortcut}" + IsEnabled="{Binding CreateShortcutEnabled}" + ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> + <Separator /> + <MenuItem + Click="OpenUserSaveDirectory_Click" + Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" + IsEnabled="{Binding OpenUserSaveDirectoryEnabled}" + ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> + <MenuItem + Click="OpenDeviceSaveDirectory_Click" + Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}" + IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}" + ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" /> + <MenuItem + Click="OpenBcatSaveDirectory_Click" + Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}" + IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}" + ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" /> + <Separator /> + <MenuItem + Click="OpenTitleUpdateManager_Click" + Header="{locale:Locale GameListContextMenuManageTitleUpdates}" + ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> + <MenuItem + Click="OpenDownloadableContentManager_Click" + Header="{locale:Locale GameListContextMenuManageDlc}" + ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> + <MenuItem + Click="OpenCheatManager_Click" + Header="{locale:Locale GameListContextMenuManageCheat}" + ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> + <MenuItem + Click="OpenModManager_Click" + Header="{locale:Locale GameListContextMenuManageMod}" + ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" /> + <Separator /> + <MenuItem + Click="OpenModsDirectory_Click" + Header="{locale:Locale GameListContextMenuOpenModsDirectory}" + ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> + <MenuItem + Click="OpenSdModsDirectory_Click" + Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" + ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> + <Separator /> + <MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}"> + <MenuItem + Click="PurgePtcCache_Click" + Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" + ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> + <MenuItem + Click="PurgeShaderCache_Click" + Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" + ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> + <MenuItem + Click="OpenPtcDirectory_Click" + Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" + ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> + <MenuItem + Click="OpenShaderCacheDirectory_Click" + Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" + ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> + </MenuItem> + <MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> + <MenuItem + Click="ExtractApplicationExeFs_Click" + Header="{locale:Locale GameListContextMenuExtractDataExeFS}" + ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" /> + <MenuItem + Click="ExtractApplicationRomFs_Click" + Header="{locale:Locale GameListContextMenuExtractDataRomFS}" + ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" /> + <MenuItem + Click="ExtractApplicationLogo_Click" + Header="{locale:Locale GameListContextMenuExtractDataLogo}" + ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> + </MenuItem> +</MenuFlyout> diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs new file mode 100644 index 00000000..894ac6c1 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs @@ -0,0 +1,371 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Threading; +using LibHac.Fs; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +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 System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.Controls +{ + public class ApplicationContextMenu : MenuFlyout + { + public ApplicationContextMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void ToggleFavorite_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; + + ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata => + { + appMetadata.Favorite = viewModel.SelectedApplication.Favorite; + }); + + viewModel.RefreshView(); + } + } + + public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args) + { + if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) + { + OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); + } + } + + public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + OpenSaveDirectory(viewModel, SaveDataType.Device, default); + } + + public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); + } + + private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) + { + if (viewModel?.SelectedApplication != null) + { + if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); + }); + + return; + } + + var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default); + + ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName); + } + } + + public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); + } + } + + public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); + } + } + + public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await new CheatWindow( + viewModel.VirtualFileSystem, + viewModel.SelectedApplication.TitleId, + viewModel.SelectedApplication.TitleName, + viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window); + } + } + + public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId); + + OpenHelper.OpenFolder(titleModsPath); + } + } + + public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string sdModsBasePath = ModLoader.GetSdModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId); + + OpenHelper.OpenFolder(titleModsPath); + } + } + + public async void OpenModManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ModManagerWindow.Show(ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName); + } + } + + public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0")); + DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1")); + + List<FileInfo> cacheFiles = new(); + + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } + + if (cacheFiles.Count > 0) + { + foreach (FileInfo file in cacheFiles) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); + } + } + } + } + } + } + + public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader")); + + List<DirectoryInfo> oldCacheDirectories = new(); + List<FileInfo> newCacheFiles = new(); + + if (shaderCacheDir.Exists) + { + oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + } + + if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) + { + foreach (DirectoryInfo directory in oldCacheDirectories) + { + try + { + directory.Delete(true); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex)); + } + } + + foreach (FileInfo file in newCacheFiles) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex)); + } + } + } + } + } + } + + public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu"); + string mainDir = Path.Combine(ptcDir, "0"); + string backupDir = Path.Combine(ptcDir, "1"); + + if (!Directory.Exists(ptcDir)) + { + Directory.CreateDirectory(ptcDir); + Directory.CreateDirectory(mainDir); + Directory.CreateDirectory(backupDir); + } + + OpenHelper.OpenFolder(ptcDir); + } + } + + public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"); + + if (!Directory.Exists(shaderCacheDir)) + { + Directory.CreateDirectory(shaderCacheDir); + } + + OpenHelper.OpenFolder(shaderCacheDir); + } + } + + public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Code, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.TitleName); + } + } + + public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Data, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.TitleName); + } + } + + public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Logo, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.TitleName); + } + } + + public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + ApplicationData selectedApplication = viewModel.SelectedApplication; + ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon); + } + } + + public async void RunApplication_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await viewModel.LoadApplication(viewModel.SelectedApplication.Path); + } + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml new file mode 100644 index 00000000..2dc95662 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml @@ -0,0 +1,102 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Controls.ApplicationGridView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + d:DesignHeight="450" + d:DesignWidth="800" + Focusable="True" + mc:Ignorable="d" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + x:DataType="viewModels:MainWindowViewModel"> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + <controls:ApplicationContextMenu x:Key="ApplicationContextMenu" /> + </UserControl.Resources> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <ListBox + Grid.Row="0" + Padding="8" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ContextFlyout="{StaticResource ApplicationContextMenu}" + DoubleTapped="GameList_DoubleTapped" + ItemsSource="{Binding AppsObservableList}" + SelectionChanged="GameList_SelectionChanged"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <WrapPanel + HorizontalAlignment="Center" + VerticalAlignment="Top" + Orientation="Horizontal" /> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Margin" Value="5" /> + <Setter Property="CornerRadius" Value="4" /> + </Style> + <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> + <Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" /> + </Style> + </ListBox.Styles> + <ListBox.ItemTemplate> + <DataTemplate> + <Grid> + <Border + Margin="10" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" + ClipToBounds="True" + CornerRadius="4"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image + Grid.Row="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Top" + Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> + <Panel + Grid.Row="1" + Height="50" + Margin="0,10,0,0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> + <TextBlock + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding TitleName}" + TextAlignment="Center" + TextWrapping="Wrap" /> + </Panel> + </Grid> + </Border> + <ui:SymbolIcon + Margin="5,5,0,0" + HorizontalAlignment="Left" + VerticalAlignment="Top" + FontSize="16" + Foreground="{DynamicResource SystemAccentColor}" + IsVisible="{Binding Favorite}" + Symbol="StarFilled" /> + </Grid> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs new file mode 100644 index 00000000..ee15bc8d --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class ApplicationGridView : UserControl + { + public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = + RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public ApplicationGridView() + { + InitializeComponent(); + } + + public void GameList_DoubleTapped(object sender, TappedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + (DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData; + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml new file mode 100644 index 00000000..fecf0888 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml @@ -0,0 +1,160 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Controls.ApplicationListView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + d:DesignHeight="450" + d:DesignWidth="800" + Focusable="True" + mc:Ignorable="d" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + x:DataType="viewModels:MainWindowViewModel"> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + <controls:ApplicationContextMenu x:Key="ApplicationContextMenu" /> + </UserControl.Resources> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <ListBox + Name="GameListBox" + Grid.Row="0" + Padding="8" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ContextFlyout="{StaticResource ApplicationContextMenu}" + DoubleTapped="GameList_DoubleTapped" + ItemsSource="{Binding AppsObservableList}" + SelectionChanged="GameList_SelectionChanged"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <StackPanel + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Orientation="Vertical" + Spacing="2" /> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.Styles> + <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> + <Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" /> + </Style> + </ListBox.Styles> + <ListBox.ItemTemplate> + <DataTemplate> + <Grid> + <Border + Margin="0" + Padding="10" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ClipToBounds="True" + CornerRadius="5"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="10" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="150" /> + <ColumnDefinition Width="100" /> + </Grid.ColumnDefinitions> + <Image + Grid.RowSpan="3" + Grid.Column="0" + Margin="0" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" + Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> + <Border + Grid.Column="2" + Margin="0,0,5,0" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="0,0,1,0"> + <StackPanel + HorizontalAlignment="Left" + VerticalAlignment="Top" + Orientation="Vertical" + Spacing="5"> + <TextBlock + HorizontalAlignment="Stretch" + FontWeight="Bold" + Text="{Binding TitleName}" + TextAlignment="Start" + TextWrapping="Wrap" /> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding Developer}" + TextAlignment="Start" + TextWrapping="Wrap" /> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding Version}" + TextAlignment="Start" + TextWrapping="Wrap" /> + </StackPanel> + </Border> + <StackPanel + Grid.Column="3" + Margin="10,0,0,0" + HorizontalAlignment="Left" + VerticalAlignment="Top" + Orientation="Vertical" + Spacing="5"> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding TitleId}" + TextAlignment="Start" + TextWrapping="Wrap" /> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding FileExtension}" + TextAlignment="Start" + TextWrapping="Wrap" /> + </StackPanel> + <StackPanel + Grid.Column="4" + HorizontalAlignment="Right" + VerticalAlignment="Top" + Orientation="Vertical" + Spacing="5"> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding TimePlayedString}" + TextAlignment="End" + TextWrapping="Wrap" /> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}" + TextAlignment="End" + TextWrapping="Wrap" /> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding FileSizeString}" + TextAlignment="End" + TextWrapping="Wrap" /> + </StackPanel> + <ui:SymbolIcon + Grid.Row="0" + Grid.Column="0" + Margin="-5,-5,0,0" + HorizontalAlignment="Left" + VerticalAlignment="Top" + FontSize="16" + Foreground="{DynamicResource SystemAccentColor}" + IsVisible="{Binding Favorite}" + Symbol="StarFilled" /> + </Grid> + </Border> + </Grid> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs new file mode 100644 index 00000000..8681158f --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class ApplicationListView : UserControl + { + public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = + RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public ApplicationListView() + { + InitializeComponent(); + } + + public void GameList_DoubleTapped(object sender, TappedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + (DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData; + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + } +} diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml new file mode 100644 index 00000000..bf34b303 --- /dev/null +++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml @@ -0,0 +1,17 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + mc:Ignorable="d" + d:DesignWidth="800" + d:DesignHeight="450" + x:Class="Ryujinx.Ava.UI.Controls.NavigationDialogHost" + Focusable="True"> + <ui:Frame + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + x:Name="ContentFrame"> + </ui:Frame> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs new file mode 100644 index 00000000..a32c052b --- /dev/null +++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs @@ -0,0 +1,217 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Views.User; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class NavigationDialogHost : UserControl + { + public AccountManager AccountManager { get; } + public ContentManager ContentManager { get; } + public VirtualFileSystem VirtualFileSystem { get; } + public HorizonClient HorizonClient { get; } + public UserProfileViewModel ViewModel { get; set; } + + public NavigationDialogHost() + { + InitializeComponent(); + } + + public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, + VirtualFileSystem virtualFileSystem, HorizonClient horizonClient) + { + AccountManager = accountManager; + ContentManager = contentManager; + VirtualFileSystem = virtualFileSystem; + HorizonClient = horizonClient; + ViewModel = new UserProfileViewModel(); + LoadProfiles(); + + if (contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem); + }); + } + InitializeComponent(); + } + + public void GoBack() + { + if (ContentFrame.BackStack.Count > 0) + { + ContentFrame.GoBack(); + } + + LoadProfiles(); + } + + public void Navigate(Type sourcePageType, object parameter) + { + ContentFrame.Navigate(sourcePageType, parameter); + } + + public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, + VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient) + { + var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = content, + Padding = new Thickness(0), + }; + + contentDialog.Closed += (sender, args) => + { + content.ViewModel.Dispose(); + }; + + Style footer = new(x => x.Name("DialogSpace").Child().OfType<Border>()); + footer.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(footer); + + await contentDialog.ShowAsync(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + Navigate(typeof(UserSelectorViews), this); + } + + public void LoadProfiles() + { + ViewModel.Profiles.Clear(); + ViewModel.LostProfiles.Clear(); + + var profiles = AccountManager.GetAllUsers().OrderBy(x => x.Name); + + foreach (var profile in profiles) + { + ViewModel.Profiles.Add(new UserProfile(profile, this)); + } + + var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, default, saveDataId: default, index: default); + + using var saveDataIterator = new UniqueRef<SaveDataIterator>(); + + HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10]; + + HashSet<UserId> lostAccounts = new(); + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + var save = saveDataInfo[i]; + var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High); + if (ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId == id) == null) + { + lostAccounts.Add(id); + } + } + } + + foreach (var account in lostAccounts) + { + ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this)); + } + + ViewModel.Profiles.Add(new BaseModel()); + } + + public async void DeleteUser(UserProfile userProfile) + { + var lastUserId = AccountManager.LastOpenedUser.UserId; + + if (userProfile.UserId == lastUserId) + { + // If we are deleting the currently open profile, then we must open something else before deleting. + var profile = ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId != lastUserId); + + if (profile == null) + { + static async void Action() + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]); + } + + Dispatcher.UIThread.Post(Action); + + return; + } + + AccountManager.OpenUser(profile.UserId); + } + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage], + "", + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result == UserResult.Yes) + { + GoBack(); + AccountManager.DeleteUser(userProfile.UserId); + } + + LoadProfiles(); + } + + public void AddUser() + { + Navigate(typeof(UserEditorView), (this, (UserProfile)null, true)); + } + + public void EditUser(UserProfile userProfile) + { + Navigate(typeof(UserEditorView), (this, userProfile, false)); + } + + public void RecoverLostAccounts() + { + Navigate(typeof(UserRecovererView), this); + } + + public void ManageSaves() + { + Navigate(typeof(UserSaveManagerView), (this, AccountManager, HorizonClient, VirtualFileSystem)); + } + } +} diff --git a/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs b/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs new file mode 100644 index 00000000..b57c6f0a --- /dev/null +++ b/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Input; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public class SliderScroll : Slider + { + protected override Type StyleKeyOverride => typeof(Slider); + + protected override void OnPointerWheelChanged(PointerWheelEventArgs e) + { + var newValue = Value + e.Delta.Y * TickFrequency; + + if (newValue < Minimum) + { + Value = Minimum; + } + else if (newValue > Maximum) + { + Value = Maximum; + } + else + { + Value = newValue; + } + + e.Handled = true; + } + } +} diff --git a/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml new file mode 100644 index 00000000..09fa0404 --- /dev/null +++ b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml @@ -0,0 +1,42 @@ +<Window + x:Class="Ryujinx.Ava.UI.Controls.UpdateWaitWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="Ryujinx - Waiting" + SizeToContent="WidthAndHeight" + WindowStartupLocation="CenterOwner" + mc:Ignorable="d" + Focusable="True"> + <Grid + Margin="20" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Image + Grid.Row="1" + Height="70" + MinWidth="50" + Margin="5,10,20,10" + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> + <StackPanel + Grid.Row="1" + Grid.Column="1" + VerticalAlignment="Center" + Orientation="Vertical"> + <TextBlock Name="PrimaryText" Margin="5" /> + <TextBlock + Name="SecondaryText" + Margin="5" + VerticalAlignment="Center" /> + </StackPanel> + </Grid> +</Window> diff --git a/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs new file mode 100644 index 00000000..7ad1ee33 --- /dev/null +++ b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Ryujinx.Ava.UI.Windows; +using System.Threading; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class UpdateWaitWindow : StyleableWindow + { + public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText) + { + SystemDecorations = SystemDecorations.Full; + ShowInTaskbar = true; + + Closing += (_, _) => cancellationToken.Cancel(); + } + + public UpdateWaitWindow(string primaryText, string secondaryText) : this() + { + PrimaryText.Text = primaryText; + SecondaryText.Text = secondaryText; + WindowStartupLocation = WindowStartupLocation.CenterOwner; + SystemDecorations = SystemDecorations.BorderOnly; + ShowInTaskbar = false; + } + + public UpdateWaitWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Helper/MetalHelper.cs b/src/Ryujinx/UI/Helper/MetalHelper.cs deleted file mode 100644 index c2c32d3a..00000000 --- a/src/Ryujinx/UI/Helper/MetalHelper.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Gdk; -using System; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace Ryujinx.UI.Helper -{ - public delegate void UpdateBoundsCallbackDelegate(Window window); - - [SupportedOSPlatform("macos")] - static partial class MetalHelper - { - private const string LibObjCImport = "/usr/lib/libobjc.A.dylib"; - - private readonly struct Selector - { - public readonly IntPtr NativePtr; - - public unsafe Selector(string value) - { - int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); - byte* data = stackalloc byte[size]; - - fixed (char* pValue = value) - { - System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); - } - - NativePtr = sel_registerName(data); - } - - public static implicit operator Selector(string value) => new(value); - } - - private static unsafe IntPtr GetClass(string value) - { - int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); - byte* data = stackalloc byte[size]; - - fixed (char* pValue = value) - { - System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); - } - - return objc_getClass(data); - } - - private struct NsPoint - { - public double X; - public double Y; - - public NsPoint(double x, double y) - { - X = x; - Y = y; - } - } - - private struct NsRect - { - public NsPoint Pos; - public NsPoint Size; - - public NsRect(double x, double y, double width, double height) - { - Pos = new NsPoint(x, y); - Size = new NsPoint(width, height); - } - } - - public static IntPtr GetMetalLayer(Display display, Window window, out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds) - { - nsView = gdk_quartz_window_get_nsview(window.Handle); - - // Create a new CAMetalLayer. - IntPtr layerClass = GetClass("CAMetalLayer"); - IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc"); - objc_msgSend(metalLayer, "init"); - - // Create a child NSView to render into. - IntPtr nsViewClass = GetClass("NSView"); - IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc"); - objc_msgSend(child, "init", new NsRect()); - - // Add it as a child. - objc_msgSend(nsView, "addSubview:", child); - - // Make its renderer our metal layer. - objc_msgSend(child, "setWantsLayer:", (byte)1); - objc_msgSend(child, "setLayer:", metalLayer); - objc_msgSend(metalLayer, "setContentsScale:", (double)display.GetMonitorAtWindow(window).ScaleFactor); - - // Set the frame position/location. - updateBounds = (Window window) => - { - window.GetPosition(out int x, out int y); - int width = window.Width; - int height = window.Height; - objc_msgSend(child, "setFrame:", new NsRect(x, y, width, height)); - }; - - updateBounds(window); - - return metalLayer; - } - - [LibraryImport(LibObjCImport)] - private static unsafe partial IntPtr sel_registerName(byte* data); - - [LibraryImport(LibObjCImport)] - private static unsafe partial IntPtr objc_getClass(byte* data); - - [LibraryImport(LibObjCImport)] - private static partial void objc_msgSend(IntPtr receiver, Selector selector); - - [LibraryImport(LibObjCImport)] - private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value); - - [LibraryImport(LibObjCImport)] - private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value); - - [LibraryImport(LibObjCImport)] - private static partial void objc_msgSend(IntPtr receiver, Selector selector, NsRect point); - - [LibraryImport(LibObjCImport)] - private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value); - - [LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")] - private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector); - - [LibraryImport("libgdk-3.0.dylib")] - private static partial IntPtr gdk_quartz_window_get_nsview(IntPtr gdkWindow); - } -} diff --git a/src/Ryujinx/UI/Helper/SortHelper.cs b/src/Ryujinx/UI/Helper/SortHelper.cs deleted file mode 100644 index 3e3fbeaa..00000000 --- a/src/Ryujinx/UI/Helper/SortHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Gtk; -using Ryujinx.UI.Common.Helper; -using System; - -namespace Ryujinx.UI.Helper -{ - static class SortHelper - { - public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) - { - TimeSpan aTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(a, 5).ToString()); - TimeSpan bTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(b, 5).ToString()); - - return TimeSpan.Compare(aTimeSpan, bTimeSpan); - } - - public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) - { - DateTime aDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(a, 6).ToString()); - DateTime bDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(b, 6).ToString()); - - return DateTime.Compare(aDateTime, bDateTime); - } - - public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) - { - long aSize = ValueFormatUtils.ParseFileSize(model.GetValue(a, 8).ToString()); - long bSize = ValueFormatUtils.ParseFileSize(model.GetValue(b, 8).ToString()); - - return aSize.CompareTo(bSize); - } - } -} diff --git a/src/Ryujinx/UI/Helper/ThemeHelper.cs b/src/Ryujinx/UI/Helper/ThemeHelper.cs deleted file mode 100644 index e1fed1c4..00000000 --- a/src/Ryujinx/UI/Helper/ThemeHelper.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Gtk; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.UI.Common.Configuration; -using System.IO; - -namespace Ryujinx.UI.Helper -{ - static class ThemeHelper - { - public static void ApplyTheme() - { - if (!ConfigurationState.Instance.UI.EnableCustomTheme) - { - return; - } - - if (File.Exists(ConfigurationState.Instance.UI.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.UI.CustomThemePath) == ".css")) - { - CssProvider cssProvider = new(); - - cssProvider.LoadFromPath(ConfigurationState.Instance.UI.CustomThemePath); - - StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); - } - else - { - Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.UI.CustomThemePath}\"."); - - ConfigurationState.Instance.UI.CustomThemePath.Value = ""; - ConfigurationState.Instance.UI.EnableCustomTheme.Value = false; - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - } -} diff --git a/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs new file mode 100644 index 00000000..bc5622b5 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs @@ -0,0 +1,16 @@ +using Avalonia.Interactivity; +using Ryujinx.UI.App.Common; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class ApplicationOpenedEventArgs : RoutedEventArgs + { + public ApplicationData Application { get; } + + public ApplicationOpenedEventArgs(ApplicationData application, RoutedEvent routedEvent) + { + Application = application; + RoutedEvent = routedEvent; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs b/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs new file mode 100644 index 00000000..42bd8d5a --- /dev/null +++ b/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs @@ -0,0 +1,36 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using System; +using System.Globalization; +using System.IO; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class BitmapArrayValueConverter : IValueConverter + { + public static BitmapArrayValueConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + if (value is byte[] buffer && targetType == typeof(IImage)) + { + MemoryStream mem = new(buffer); + + return new Bitmap(mem); + } + + throw new NotSupportedException(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs new file mode 100644 index 00000000..7e8ba734 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs @@ -0,0 +1,118 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.LogicalTree; +using Avalonia.Threading; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class ButtonKeyAssigner + { + internal class ButtonAssignedEventArgs : EventArgs + { + public ToggleButton Button { get; } + public bool IsAssigned { get; } + + public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned) + { + Button = button; + IsAssigned = isAssigned; + } + } + + public ToggleButton ToggledButton { get; set; } + + private bool _isWaitingForInput; + private bool _shouldUnbind; + public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned; + + public ButtonKeyAssigner(ToggleButton toggleButton) + { + ToggledButton = toggleButton; + } + + public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) + { + Dispatcher.UIThread.Post(() => + { + ToggledButton.IsChecked = true; + }); + + if (_isWaitingForInput) + { + Dispatcher.UIThread.Post(() => + { + Cancel(); + }); + + return; + } + + _isWaitingForInput = true; + + assigner.Initialize(); + + await Task.Run(async () => + { + while (true) + { + if (!_isWaitingForInput) + { + return; + } + + await Task.Delay(10); + + assigner.ReadInput(); + + if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) + { + break; + } + } + }); + + await Dispatcher.UIThread.InvokeAsync(() => + { + string pressedButton = assigner.GetPressedButton(); + + if (_shouldUnbind) + { + SetButtonText(ToggledButton, "Unbound"); + } + else if (pressedButton != "") + { + SetButtonText(ToggledButton, pressedButton); + } + + _shouldUnbind = false; + _isWaitingForInput = false; + + ToggledButton.IsChecked = false; + + ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null)); + + static void SetButtonText(ToggleButton button, string text) + { + ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock); + + if (textBlock != null && textBlock is TextBlock block) + { + block.Text = text; + } + } + }); + } + + public void Cancel(bool shouldUnbind = false) + { + _isWaitingForInput = false; + ToggledButton.IsChecked = false; + _shouldUnbind = shouldUnbind; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs new file mode 100644 index 00000000..15b7ddd1 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs @@ -0,0 +1,425 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Threading; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class ContentDialogHelper + { + private static bool _isChoiceDialogOpen; + private static ContentDialogOverlayWindow _contentDialogOverlayWindow; + + private async static Task<UserResult> ShowContentDialog( + string title, + object content, + string primaryButton, + string secondaryButton, + string closeButton, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) + { + UserResult result = UserResult.None; + + ContentDialog contentDialog = new() + { + Title = title, + PrimaryButtonText = primaryButton, + SecondaryButtonText = secondaryButton, + CloseButtonText = closeButton, + Content = content, + PrimaryButtonCommand = MiniCommand.Create(() => + { + result = primaryButtonResult; + }), + }; + + contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => + { + result = UserResult.No; + contentDialog.PrimaryButtonClick -= deferCloseAction; + }); + + contentDialog.CloseButtonCommand = MiniCommand.Create(() => + { + result = UserResult.Cancel; + contentDialog.PrimaryButtonClick -= deferCloseAction; + }); + + if (deferResetEvent != null) + { + contentDialog.PrimaryButtonClick += deferCloseAction; + } + + await ShowAsync(contentDialog); + + return result; + } + + public async static Task<UserResult> ShowTextDialog( + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null) + { + Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol); + + return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); + } + + public async static Task<UserResult> ShowDeferredContentDialog( + StyleableWindow window, + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + ManualResetEvent deferResetEvent, + Func<Window, Task> doWhileDeferred = null) + { + bool startedDeferring = false; + + return await ShowTextDialog( + title, + primaryText, + secondaryText, + primaryButton, + secondaryButton, + closeButton, + iconSymbol, + primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok, + deferResetEvent, + DeferClose); + + async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + if (startedDeferring) + { + return; + } + + sender.PrimaryButtonClick -= DeferClose; + + startedDeferring = true; + + var deferral = args.GetDeferral(); + + sender.PrimaryButtonClick -= DeferClose; + + _ = Task.Run(() => + { + deferResetEvent.WaitOne(); + + Dispatcher.UIThread.Post(() => + { + deferral.Complete(); + }); + }); + + if (doWhileDeferred != null) + { + await doWhileDeferred(window); + + deferResetEvent.Set(); + } + } + } + + private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol) + { + Grid content = new() + { + RowDefinitions = new RowDefinitions { new(), new() }, + ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() }, + + MinHeight = 80, + }; + + SymbolIcon icon = new() + { + Symbol = (Symbol)symbol, + Margin = new Thickness(10), + FontSize = 40, + VerticalAlignment = VerticalAlignment.Center, + }; + + Grid.SetColumn(icon, 0); + Grid.SetRowSpan(icon, 2); + Grid.SetRow(icon, 0); + + TextBlock primaryLabel = new() + { + Text = primaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + }; + + TextBlock secondaryLabel = new() + { + Text = secondaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + }; + + Grid.SetColumn(primaryLabel, 1); + Grid.SetColumn(secondaryLabel, 1); + Grid.SetRow(primaryLabel, 0); + Grid.SetRow(secondaryLabel, 1); + + content.Children.Add(icon); + content.Children.Add(primaryLabel); + content.Children.Add(secondaryLabel); + + return content; + } + + public static async Task<UserResult> CreateInfoDialog( + string primary, + string secondaryText, + string acceptButton, + string closeButton, + string title) + { + return await ShowTextDialog( + title, + primary, + secondaryText, + acceptButton, + "", + closeButton, + (int)Symbol.Important); + } + + internal static async Task<UserResult> CreateConfirmationDialog( + string primaryText, + string secondaryText, + string acceptButtonText, + string cancelButtonText, + string title, + UserResult primaryButtonResult = UserResult.Yes) + { + return await ShowTextDialog( + string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title, + primaryText, + secondaryText, + acceptButtonText, + "", + cancelButtonText, + (int)Symbol.Help, + primaryButtonResult); + } + + internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) + { + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], + primary, + secondaryText, + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Important); + } + + internal static async Task CreateWarningDialog(string primary, string secondaryText) + { + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogWarningTitle], + primary, + secondaryText, + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Important); + } + + internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "") + { + Logger.Error?.Print(LogClass.Application, errorMessage); + + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogErrorTitle], + LocaleManager.Instance[LocaleKeys.DialogErrorMessage], + errorMessage, + secondaryErrorMessage, + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Dismiss); + } + + internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText) + { + if (_isChoiceDialogOpen) + { + return false; + } + + _isChoiceDialogOpen = true; + + UserResult response = await ShowTextDialog( + title, + primary, + secondaryText, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + "", + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help, + UserResult.Yes); + + _isChoiceDialogOpen = false; + + return response == UserResult.Yes; + } + + internal static async Task<bool> CreateExitDialog() + { + return await CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogExitTitle], + LocaleManager.Instance[LocaleKeys.DialogExitMessage], + LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); + } + + internal static async Task<bool> CreateStopEmulationDialog() + { + return await CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogStopEmulationTitle], + LocaleManager.Instance[LocaleKeys.DialogStopEmulationMessage], + LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); + } + + public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog) + { + ContentDialogResult result; + bool isTopDialog = true; + + Window parent = GetMainWindow(); + + if (_contentDialogOverlayWindow != null) + { + isTopDialog = false; + } + + if (parent is MainWindow window) + { + parent.Activate(); + + _contentDialogOverlayWindow = new ContentDialogOverlayWindow + { + Height = parent.Bounds.Height, + Width = parent.Bounds.Width, + Position = parent.PointToScreen(new Point()), + ShowInTaskbar = false, + }; + + parent.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + if (_contentDialogOverlayWindow is null) + { + return; + } + + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + } + + _contentDialogOverlayWindow.ContentDialog = contentDialog; + + bool opened = false; + + _contentDialogOverlayWindow.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + + result = await ShowDialog(); + } + + result = await _contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent); + } + else + { + result = await ShowDialog(); + } + + async Task<ContentDialogResult> ShowDialog() + { + if (_contentDialogOverlayWindow is not null) + { + result = await contentDialog.ShowAsync(_contentDialogOverlayWindow); + + _contentDialogOverlayWindow!.Close(); + } + else + { + result = ContentDialogResult.None; + + Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned."); + } + + return result; + } + + if (isTopDialog && _contentDialogOverlayWindow is not null) + { + _contentDialogOverlayWindow.Content = null; + _contentDialogOverlayWindow.Close(); + _contentDialogOverlayWindow = null; + } + + return result; + } + + public static Task ShowWindowAsync(Window dialogWindow, Window mainWindow = null) + { + mainWindow ??= GetMainWindow(); + + return dialogWindow.ShowDialog(_contentDialogOverlayWindow ?? mainWindow); + } + + private static Window GetMainWindow() + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + { + foreach (Window item in al.Windows) + { + if (item is MainWindow window) + { + return window; + } + } + } + + return null; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/Glyph.cs b/src/Ryujinx/UI/Helpers/Glyph.cs new file mode 100644 index 00000000..f257dc02 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Glyph.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ava.UI.Helpers +{ + public enum Glyph + { + List, + Grid, + Chip, + } +} diff --git a/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs b/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs new file mode 100644 index 00000000..7da23648 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs @@ -0,0 +1,42 @@ +using Avalonia.Markup.Xaml; +using FluentAvalonia.UI.Controls; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class GlyphValueConverter : MarkupExtension + { + private readonly string _key; + + private static readonly Dictionary<Glyph, string> _glyphs = new() + { + { Glyph.List, char.ConvertFromUtf32((int)Symbol.List) }, + { Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll) }, + { Glyph.Chip, char.ConvertFromUtf32(59748) }, + }; + + public GlyphValueConverter(string key) + { + _key = key; + } + + public string this[string key] + { + get + { + if (_glyphs.TryGetValue(Enum.Parse<Glyph>(key), out var val)) + { + return val; + } + + return string.Empty; + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this[_key]; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs new file mode 100644 index 00000000..028ed6bf --- /dev/null +++ b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs @@ -0,0 +1,46 @@ +using Avalonia.Data.Converters; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class KeyValueConverter : IValueConverter + { + public static KeyValueConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + object key = null; + + if (value != null) + { + if (targetType == typeof(Key)) + { + key = Enum.Parse<Key>(value.ToString()); + } + else if (targetType == typeof(GamepadInputId)) + { + key = Enum.Parse<GamepadInputId>(value.ToString()); + } + else if (targetType == typeof(StickInputId)) + { + key = Enum.Parse<StickInputId>(value.ToString()); + } + } + + return key; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs new file mode 100644 index 00000000..26fe36c4 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs @@ -0,0 +1,43 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.UI.Common.Helper; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + /// <summary> + /// This <see cref="IValueConverter"/> makes sure that the string "Never" that's returned by <see cref="ValueFormatUtils.FormatDateTime"/> is properly localized in the Avalonia UI. + /// After the Avalonia UI has been made the default and the GTK UI is removed, <see cref="ValueFormatUtils"/> should be updated to directly return a localized string. + /// </summary> + internal class LocalizedNeverConverter : MarkupExtension, IValueConverter + { + private static readonly LocalizedNeverConverter _instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not string valStr) + { + return ""; + } + + if (valStr == "Never") + { + return LocaleManager.Instance[LocaleKeys.Never]; + } + + return valStr; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return _instance; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs new file mode 100644 index 00000000..fc714541 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs @@ -0,0 +1,102 @@ +using Avalonia.Logging; +using Avalonia.Utilities; +using Ryujinx.Common.Logging; +using System; +using System.Text; + +namespace Ryujinx.Ava.UI.Helpers +{ + using AvaLogger = Avalonia.Logging.Logger; + using AvaLogLevel = LogEventLevel; + using RyuLogClass = LogClass; + using RyuLogger = Ryujinx.Common.Logging.Logger; + + internal class LoggerAdapter : ILogSink + { + public static void Register() + { + AvaLogger.Sink = new LoggerAdapter(); + } + + private static RyuLogger.Log? GetLog(AvaLogLevel level) + { + return level switch + { + AvaLogLevel.Verbose => RyuLogger.Debug, + AvaLogLevel.Debug => RyuLogger.Debug, + AvaLogLevel.Information => RyuLogger.Debug, + AvaLogLevel.Warning => RyuLogger.Debug, + AvaLogLevel.Error => RyuLogger.Error, + AvaLogLevel.Fatal => RyuLogger.Error, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null), + }; + } + + public bool IsEnabled(AvaLogLevel level, string area) + { + return GetLog(level) != null; + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate) + { + 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)); + } + + private static string Format(AvaLogLevel level, string area, string template, object source, object[] v) + { + var result = new StringBuilder(); + var r = new CharacterReader(template.AsSpan()); + int i = 0; + + result.Append('['); + result.Append(level); + result.Append("] "); + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source != null) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/MiniCommand.cs b/src/Ryujinx/UI/Helpers/MiniCommand.cs new file mode 100644 index 00000000..7e1bb9a6 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/MiniCommand.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Ryujinx.Ava.UI.Helpers +{ + public sealed class MiniCommand<T> : MiniCommand, ICommand + { + private readonly Action<T> _callback; + private bool _busy; + private readonly Func<T, Task> _asyncCallback; + + public MiniCommand(Action<T> callback) + { + _callback = callback; + } + + public MiniCommand(Func<T, Task> callback) + { + _asyncCallback = callback; + } + + private bool Busy + { + get => _busy; + set + { + _busy = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + public override event EventHandler CanExecuteChanged; + public override bool CanExecute(object parameter) => !_busy; + + public override async void Execute(object parameter) + { + if (Busy) + { + return; + } + try + { + Busy = true; + if (_callback != null) + { + _callback((T)parameter); + } + else + { + await _asyncCallback((T)parameter); + } + } + finally + { + Busy = false; + } + } + } + + public abstract class MiniCommand : ICommand + { + public static MiniCommand Create(Action callback) => new MiniCommand<object>(_ => callback()); + public static MiniCommand Create<TArg>(Action<TArg> callback) => new MiniCommand<TArg>(callback); + public static MiniCommand CreateFromTask(Func<Task> callback) => new MiniCommand<object>(_ => callback()); + + public abstract bool CanExecute(object parameter); + public abstract void Execute(object parameter); + public abstract event EventHandler CanExecuteChanged; + } +} diff --git a/src/Ryujinx/UI/Helpers/NotificationHelper.cs b/src/Ryujinx/UI/Helpers/NotificationHelper.cs new file mode 100644 index 00000000..656a8b52 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/NotificationHelper.cs @@ -0,0 +1,70 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class NotificationHelper + { + private const int MaxNotifications = 4; + private const int NotificationDelayInMs = 5000; + + private static WindowNotificationManager _notificationManager; + + private static readonly BlockingCollection<Notification> _notifications = new(); + + public static void SetNotificationManager(Window host) + { + _notificationManager = new WindowNotificationManager(host) + { + Position = NotificationPosition.BottomRight, + MaxItems = MaxNotifications, + Margin = new Thickness(0, 0, 15, 40), + }; + + var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>( + () => new AsyncWorkQueue<Notification>(notification => + { + Dispatcher.UIThread.Post(() => + { + _notificationManager.Show(notification); + }); + }, + "UI.NotificationThread", + _notifications), + LazyThreadSafetyMode.ExecutionAndPublication); + + _notificationManager.TemplateApplied += (sender, args) => + { + // NOTE: Force creation of the AsyncWorkQueue. + _ = maybeAsyncWorkQueue.Value; + }; + + host.Closing += (sender, args) => + { + if (maybeAsyncWorkQueue.IsValueCreated) + { + maybeAsyncWorkQueue.Value.Dispose(); + } + }; + } + + public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null) + { + var delay = waitingExit ? TimeSpan.FromMilliseconds(0) : TimeSpan.FromMilliseconds(NotificationDelayInMs); + + _notifications.Add(new Notification(title, text, type, delay, onClick, onClose)); + } + + public static void ShowError(string message) + { + Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs new file mode 100644 index 00000000..a055f335 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs @@ -0,0 +1,39 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class OffscreenTextBox : TextBox + { + public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent() + { + return KeyDownEvent; + } + + public static RoutedEvent<KeyEventArgs> GetKeyUpRoutedEvent() + { + return KeyUpEvent; + } + + public void SendKeyDownEvent(KeyEventArgs keyEvent) + { + OnKeyDown(keyEvent); + } + + public void SendKeyUpEvent(KeyEventArgs keyEvent) + { + OnKeyUp(keyEvent); + } + + public void SendText(string text) + { + OnTextInput(new TextInputEventArgs + { + Text = text, + Source = this, + RoutedEvent = TextInputEvent, + }); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs b/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs new file mode 100644 index 00000000..876d51f7 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs @@ -0,0 +1,28 @@ +using Avalonia.Data.Converters; +using System; +using System.Globalization; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class TimeZoneConverter : IValueConverter + { + public static TimeZoneConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + var timeZone = (TimeZone)value; + return string.Format("{0} {1} {2}", timeZone.UtcDifference, timeZone.Location, timeZone.Abbreviation); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx/UI/Helpers/UserErrorDialog.cs new file mode 100644 index 00000000..9a44b862 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/UserErrorDialog.cs @@ -0,0 +1,90 @@ +using Ryujinx.Ava.Common.Locale; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class UserErrorDialog + { + private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; + + private static string GetErrorCode(UserError error) + { + return $"RYU-{(uint)error:X4}"; + } + + private static string GetErrorTitle(UserError error) + { + return error switch + { + UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys], + UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware], + UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed], + UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound], + UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown], + _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined], + }; + } + + private static string GetErrorDescription(UserError error) + { + return error switch + { + UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription], + UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription], + UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription], + UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription], + UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription], + _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription], + }; + } + + private static bool IsCoveredBySetupGuide(UserError error) + { + return error switch + { + UserError.NoKeys or + UserError.NoFirmware or + UserError.FirmwareParsingFailed => true, + _ => false, + }; + } + + private static string GetSetupGuideUrl(UserError error) + { + if (!IsCoveredBySetupGuide(error)) + { + return null; + } + + return error switch + { + UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", + UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", + _ => SetupGuideUrl, + }; + } + + public static async Task ShowUserErrorDialog(UserError error) + { + string errorCode = GetErrorCode(error); + + bool isInSetupGuide = IsCoveredBySetupGuide(error); + + string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : ""; + + var result = await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)), + GetErrorDescription(error) + (isInSetupGuide + ? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage] + : ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode)); + + if (result == UserResult.Ok) + { + OpenHelper.OpenUrl(GetSetupGuideUrl(error)); + } + } + } +} diff --git a/src/Ryujinx/UI/Helpers/UserResult.cs b/src/Ryujinx/UI/Helpers/UserResult.cs new file mode 100644 index 00000000..2fcd35ae --- /dev/null +++ b/src/Ryujinx/UI/Helpers/UserResult.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Ava.UI.Helpers +{ + public enum UserResult + { + Ok, + Yes, + No, + Abort, + Cancel, + None, + } +} diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs new file mode 100644 index 00000000..4834df80 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -0,0 +1,125 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Ava.UI.Helpers +{ + [SupportedOSPlatform("windows")] + internal partial class Win32NativeInterop + { + [Flags] + public enum ClassStyles : uint + { + CsClassdc = 0x40, + CsOwndc = 0x20, + } + + [Flags] + public enum WindowStyles : uint + { + WsChild = 0x40000000, + } + + public enum Cursors : uint + { + IdcArrow = 32512, + } + + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + public enum WindowsMessages : uint + { + Mousemove = 0x0200, + Lbuttondown = 0x0201, + Lbuttonup = 0x0202, + Lbuttondblclk = 0x0203, + Rbuttondown = 0x0204, + Rbuttonup = 0x0205, + Rbuttondblclk = 0x0206, + Mbuttondown = 0x0207, + Mbuttonup = 0x0208, + Mbuttondblclk = 0x0209, + Mousewheel = 0x020A, + Xbuttondown = 0x020B, + Xbuttonup = 0x020C, + Xbuttondblclk = 0x020D, + Mousehwheel = 0x020E, + Mouselast = 0x020E, + } + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential)] + public struct WndClassEx + { + public int cbSize; + public ClassStyles style; + public IntPtr lpfnWndProc; // not WndProc + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + public IntPtr lpszMenuName; + public IntPtr lpszClassName; + public IntPtr hIconSm; + + public WndClassEx() + { + cbSize = Marshal.SizeOf<WndClassEx>(); + } + } + + public static IntPtr CreateEmptyCursor() + { + return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 }); + } + + public static IntPtr CreateArrowCursor() + { + return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IdcArrow); + } + + [LibraryImport("user32.dll")] + public static partial IntPtr SetCursor(IntPtr handle); + + [LibraryImport("user32.dll")] + public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, [In] byte[] pvAndPlane, [In] byte[] pvXorPlane); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")] + public static partial ushort RegisterClassEx(ref WndClassEx param); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "UnregisterClassW")] + public static partial short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance); + + [LibraryImport("user32.dll", EntryPoint = "DefWindowProcW")] + public static partial IntPtr DefWindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleA")] + public static partial IntPtr GetModuleHandle([MarshalAs(UnmanagedType.LPStr)] string lpModuleName); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool DestroyWindow(IntPtr hwnd); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "LoadCursorA")] + public static partial IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowExW")] + public static partial IntPtr CreateWindowEx( + uint dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, + WindowStyles dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + } +} diff --git a/src/Ryujinx/UI/MainWindow.cs b/src/Ryujinx/UI/MainWindow.cs deleted file mode 100644 index 2908f1a8..00000000 --- a/src/Ryujinx/UI/MainWindow.cs +++ /dev/null @@ -1,1941 +0,0 @@ -using Gtk; -using LibHac.Common; -using LibHac.Common.Keys; -using LibHac.Ncm; -using LibHac.Ns; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Audio.Backends.Dummy; -using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; -using Ryujinx.Audio.Backends.SoundIo; -using Ryujinx.Audio.Integration; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Multiplayer; -using Ryujinx.Common.Logging; -using Ryujinx.Common.SystemInterop; -using Ryujinx.Cpu; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.GAL.Multithreading; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.Input.GTK3; -using Ryujinx.Input.HLE; -using Ryujinx.Input.SDL2; -using Ryujinx.Modules; -using Ryujinx.UI.App.Common; -using Ryujinx.UI.Applet; -using Ryujinx.UI.Common; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using Ryujinx.UI.Helper; -using Ryujinx.UI.Widgets; -using Ryujinx.UI.Windows; -using Silk.NET.Vulkan; -using SPB.Graphics.Vulkan; -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using GUI = Gtk.Builder.ObjectAttribute; -using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; - -namespace Ryujinx.UI -{ - public class MainWindow : Window - { - private readonly VirtualFileSystem _virtualFileSystem; - private readonly ContentManager _contentManager; - private readonly AccountManager _accountManager; - private readonly LibHacHorizonManager _libHacHorizonManager; - - private UserChannelPersistence _userChannelPersistence; - - private HLE.Switch _emulationContext; - - private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; - - private readonly ApplicationLibrary _applicationLibrary; - private readonly GtkHostUIHandler _uiHandler; - private readonly AutoResetEvent _deviceExitStatus; - private readonly ListStore _tableStore; - - private bool _updatingGameTable; - private bool _gameLoaded; - private bool _ending; - - private string _currentEmulatedGamePath = null; - - private string _lastScannedAmiiboId = ""; - private bool _lastScannedAmiiboShowAll = false; - - public RendererWidgetBase RendererWidget; - public InputManager InputManager; - - public bool IsFocused; - -#pragma warning disable CS0169, CS0649, IDE0044, IDE0051 // Field is never assigned to, Add readonly modifier, Remove unused private member - - [GUI] public MenuItem ExitMenuItem; - [GUI] public MenuItem UpdateMenuItem; - [GUI] MenuBar _menuBar; - [GUI] Box _footerBox; - [GUI] Box _statusBar; - [GUI] MenuItem _optionMenu; - [GUI] MenuItem _manageUserProfiles; - [GUI] MenuItem _fileMenu; - [GUI] MenuItem _loadApplicationFile; - [GUI] MenuItem _loadApplicationFolder; - [GUI] MenuItem _appletMenu; - [GUI] MenuItem _actionMenu; - [GUI] MenuItem _pauseEmulation; - [GUI] MenuItem _resumeEmulation; - [GUI] MenuItem _stopEmulation; - [GUI] MenuItem _simulateWakeUpMessage; - [GUI] MenuItem _scanAmiibo; - [GUI] MenuItem _takeScreenshot; - [GUI] MenuItem _hideUI; - [GUI] MenuItem _fullScreen; - [GUI] CheckMenuItem _startFullScreen; - [GUI] CheckMenuItem _showConsole; - [GUI] CheckMenuItem _favToggle; - [GUI] MenuItem _firmwareInstallDirectory; - [GUI] MenuItem _firmwareInstallFile; - [GUI] MenuItem _fileTypesSubMenu; - [GUI] Label _fifoStatus; - [GUI] CheckMenuItem _iconToggle; - [GUI] CheckMenuItem _developerToggle; - [GUI] CheckMenuItem _appToggle; - [GUI] CheckMenuItem _timePlayedToggle; - [GUI] CheckMenuItem _versionToggle; - [GUI] CheckMenuItem _lastPlayedToggle; - [GUI] CheckMenuItem _fileExtToggle; - [GUI] CheckMenuItem _pathToggle; - [GUI] CheckMenuItem _fileSizeToggle; - [GUI] CheckMenuItem _nspShown; - [GUI] CheckMenuItem _pfs0Shown; - [GUI] CheckMenuItem _xciShown; - [GUI] CheckMenuItem _ncaShown; - [GUI] CheckMenuItem _nroShown; - [GUI] CheckMenuItem _nsoShown; - [GUI] Label _gpuBackend; - [GUI] Label _dockedMode; - [GUI] Label _aspectRatio; - [GUI] Label _gameStatus; - [GUI] TreeView _gameTable; - [GUI] TreeSelection _gameTableSelection; - [GUI] ScrolledWindow _gameTableWindow; - [GUI] Label _gpuName; - [GUI] Label _progressLabel; - [GUI] Label _firmwareVersionLabel; - [GUI] Gtk.ProgressBar _progressBar; - [GUI] Box _viewBox; - [GUI] Label _vSyncStatus; - [GUI] Label _volumeStatus; - [GUI] Box _listStatusBox; - [GUI] Label _loadingStatusLabel; - [GUI] Gtk.ProgressBar _loadingStatusBar; - -#pragma warning restore CS0649, IDE0044, CS0169, IDE0051 - - public MainWindow() : this(new Builder("Ryujinx.UI.MainWindow.glade")) { } - - private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) - { - builder.Autoconnect(this); - - // Apply custom theme if needed. - ThemeHelper.ApplyTheme(); - - SetWindowSizePosition(); - - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - Title = $"Ryujinx {Program.Version}"; - - // Hide emulation context status bar. - _statusBar.Hide(); - - // Instantiate HLE objects. - _virtualFileSystem = VirtualFileSystem.CreateInstance(); - _libHacHorizonManager = new LibHacHorizonManager(); - - _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); - _libHacHorizonManager.InitializeArpServer(); - _libHacHorizonManager.InitializeBcatServer(); - _libHacHorizonManager.InitializeSystemClients(); - - // Save data created before we supported extra data in directory save data will not work properly if - // given empty extra data. Luckily some of that extra data can be created using the data from the - // save data indexer, which should be enough to check access permissions for user saves. - // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. - // Consider removing this at some point in the future when we don't need to worry about old saves. - VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient); - - _contentManager = new ContentManager(_virtualFileSystem); - _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile); - _userChannelPersistence = new UserChannelPersistence(); - - // Instantiate GUI objects. - _applicationLibrary = new ApplicationLibrary(_virtualFileSystem); - _uiHandler = new GtkHostUIHandler(this); - _deviceExitStatus = new AutoResetEvent(false); - - WindowStateEvent += WindowStateEvent_Changed; - DeleteEvent += Window_Close; - FocusInEvent += MainWindow_FocusInEvent; - FocusOutEvent += MainWindow_FocusOutEvent; - - _applicationLibrary.ApplicationAdded += Application_Added; - _applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; - - _fileMenu.StateChanged += FileMenu_StateChanged; - _actionMenu.StateChanged += ActionMenu_StateChanged; - _optionMenu.StateChanged += OptionMenu_StateChanged; - - _gameTable.ButtonReleaseEvent += Row_Clicked; - _fullScreen.Activated += FullScreen_Toggled; - - RendererWidgetBase.StatusUpdatedEvent += Update_StatusBar; - - ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; - ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; - ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; - ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; - - ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode; - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId; - - if (ConfigurationState.Instance.UI.StartFullscreen) - { - _startFullScreen.Active = true; - } - - _showConsole.Active = ConfigurationState.Instance.UI.ShowConsole.Value; - _showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported; - - _actionMenu.Sensitive = false; - _pauseEmulation.Sensitive = false; - _resumeEmulation.Sensitive = false; - - _nspShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value; - _pfs0Shown.Active = ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value; - _xciShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value; - _ncaShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value; - _nroShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value; - _nsoShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value; - - _nspShown.Toggled += NSP_Shown_Toggled; - _pfs0Shown.Toggled += PFS0_Shown_Toggled; - _xciShown.Toggled += XCI_Shown_Toggled; - _ncaShown.Toggled += NCA_Shown_Toggled; - _nroShown.Toggled += NRO_Shown_Toggled; - _nsoShown.Toggled += NSO_Shown_Toggled; - - _fileTypesSubMenu.Visible = FileAssociationHelper.IsTypeAssociationSupported; - - if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) - { - _favToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) - { - _iconToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) - { - _appToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) - { - _developerToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) - { - _versionToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) - { - _timePlayedToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) - { - _lastPlayedToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) - { - _fileExtToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) - { - _fileSizeToggle.Active = true; - } - if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) - { - _pathToggle.Active = true; - } - - _favToggle.Toggled += Fav_Toggled; - _iconToggle.Toggled += Icon_Toggled; - _appToggle.Toggled += App_Toggled; - _developerToggle.Toggled += Developer_Toggled; - _versionToggle.Toggled += Version_Toggled; - _timePlayedToggle.Toggled += TimePlayed_Toggled; - _lastPlayedToggle.Toggled += LastPlayed_Toggled; - _fileExtToggle.Toggled += FileExt_Toggled; - _fileSizeToggle.Toggled += FileSize_Toggled; - _pathToggle.Toggled += Path_Toggled; - - _gameTable.Model = _tableStore = new ListStore( - typeof(bool), - typeof(Gdk.Pixbuf), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(BlitStruct<ApplicationControlProperty>)); - - _tableStore.SetSortFunc(5, SortHelper.TimePlayedSort); - _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort); - _tableStore.SetSortFunc(8, SortHelper.FileSizeSort); - - int columnId = ConfigurationState.Instance.UI.ColumnSort.SortColumnId; - bool ascending = ConfigurationState.Instance.UI.ColumnSort.SortAscending; - - _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending); - - _gameTable.EnableSearch = true; - _gameTable.SearchColumn = 2; - _gameTable.SearchEqualFunc = (model, col, key, iter) => !((string)model.GetValue(iter, col)).Contains(key, StringComparison.InvariantCultureIgnoreCase); - - _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); - - UpdateColumns(); - UpdateGameTable(); - - ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => - { - if (args.OldValue != args.NewValue) - { - UpdateGameTable(); - } - }; - - Task.Run(RefreshFirmwareLabel); - - InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver()); - } - - private void UpdateMultiplayerLanInterfaceId(object sender, ReactiveEventArgs<string> args) - { - if (_emulationContext != null) - { - _emulationContext.Configuration.MultiplayerLanInterfaceId = args.NewValue; - } - } - - private void UpdateMultiplayerMode(object sender, ReactiveEventArgs<MultiplayerMode> args) - { - if (_emulationContext != null) - { - _emulationContext.Configuration.MultiplayerMode = args.NewValue; - } - } - - private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args) - { - if (_emulationContext != null) - { - _emulationContext.Configuration.IgnoreMissingServices = args.NewValue; - } - } - - private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args) - { - if (_emulationContext != null) - { - _emulationContext.Configuration.AspectRatio = args.NewValue; - } - } - - private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e) - { - _emulationContext?.System.ChangeDockedModeState(e.NewValue); - } - - private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e) - { - _emulationContext?.SetVolume(e.NewValue); - } - - private void WindowStateEvent_Changed(object o, WindowStateEventArgs args) - { - _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen"; - } - - private void MainWindow_FocusOutEvent(object o, FocusOutEventArgs args) - { - IsFocused = false; - } - - private void MainWindow_FocusInEvent(object o, FocusInEventArgs args) - { - IsFocused = true; - } - - private void UpdateColumns() - { - foreach (TreeViewColumn column in _gameTable.Columns) - { - _gameTable.RemoveColumn(column); - } - - CellRendererToggle favToggle = new(); - favToggle.Toggled += FavToggle_Toggled; - - if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) - { - _gameTable.AppendColumn("Fav", favToggle, "active", 0); - } - if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) - { - _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); - } - if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) - { - _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); - } - if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) - { - _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); - } - if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) - { - _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); - } - if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) - { - _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); - } - if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) - { - _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); - } - if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) - { - _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); - } - if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) - { - _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); - } - if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) - { - _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); - } - - foreach (TreeViewColumn column in _gameTable.Columns) - { - switch (column.Title) - { - case "Fav": - column.SortColumnId = 0; - column.Clicked += Column_Clicked; - break; - case "Application": - column.SortColumnId = 2; - column.Clicked += Column_Clicked; - break; - case "Developer": - column.SortColumnId = 3; - column.Clicked += Column_Clicked; - break; - case "Version": - column.SortColumnId = 4; - column.Clicked += Column_Clicked; - break; - case "Time Played": - column.SortColumnId = 5; - column.Clicked += Column_Clicked; - break; - case "Last Played": - column.SortColumnId = 6; - column.Clicked += Column_Clicked; - break; - case "File Ext": - column.SortColumnId = 7; - column.Clicked += Column_Clicked; - break; - case "File Size": - column.SortColumnId = 8; - column.Clicked += Column_Clicked; - break; - case "Path": - column.SortColumnId = 9; - column.Clicked += Column_Clicked; - break; - } - } - } - - protected override void OnDestroyed() - { - InputManager.Dispose(); - } - - private void InitializeSwitchInstance() - { - _virtualFileSystem.ReloadKeySet(); - - IRenderer renderer; - - if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) - { - string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; - renderer = new Graphics.Vulkan.VulkanRenderer(Vk.GetApi(), CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); - } - else - { - renderer = new Graphics.OpenGL.OpenGLRenderer(); - } - - BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - - bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); - - if (threadedGAL) - { - renderer = new ThreadedRenderer(renderer); - } - - Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {threadedGAL}"); - - IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); - - if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) - { - if (SDL2HardwareDeviceDriver.IsSupported) - { - deviceDriver = new SDL2HardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); - - if (OpenALHardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; - SaveConfig(); - - deviceDriver = new OpenALHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); - - if (SoundIoHardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; - SaveConfig(); - - deviceDriver = new SoundIoHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); - } - } - } - } - else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) - { - if (SoundIoHardwareDeviceDriver.IsSupported) - { - deviceDriver = new SoundIoHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2."); - - if (SDL2HardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; - SaveConfig(); - - deviceDriver = new SDL2HardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); - - if (OpenALHardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; - SaveConfig(); - - deviceDriver = new OpenALHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out."); - } - } - } - } - else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) - { - if (OpenALHardwareDeviceDriver.IsSupported) - { - deviceDriver = new OpenALHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2."); - - if (SDL2HardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; - SaveConfig(); - - deviceDriver = new SDL2HardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO."); - - if (SoundIoHardwareDeviceDriver.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; - SaveConfig(); - - deviceDriver = new SoundIoHardwareDeviceDriver(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); - } - } - } - } - - var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value - ? HLE.MemoryConfiguration.MemoryConfiguration6GiB - : HLE.MemoryConfiguration.MemoryConfiguration4GiB; - - IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; - - HLE.HLEConfiguration configuration = new(_virtualFileSystem, - _libHacHorizonManager, - _contentManager, - _accountManager, - _userChannelPersistence, - renderer, - deviceDriver, - memoryConfiguration, - _uiHandler, - (SystemLanguage)ConfigurationState.Instance.System.Language.Value, - (RegionCode)ConfigurationState.Instance.System.Region.Value, - ConfigurationState.Instance.Graphics.EnableVsync, - ConfigurationState.Instance.System.EnableDockedMode, - ConfigurationState.Instance.System.EnablePtc, - ConfigurationState.Instance.System.EnableInternetAccess, - fsIntegrityCheckLevel, - ConfigurationState.Instance.System.FsGlobalAccessLogMode, - ConfigurationState.Instance.System.SystemTimeOffset, - ConfigurationState.Instance.System.TimeZone, - ConfigurationState.Instance.System.MemoryManagerMode, - ConfigurationState.Instance.System.IgnoreMissingServices, - ConfigurationState.Instance.Graphics.AspectRatio, - ConfigurationState.Instance.System.AudioVolume, - ConfigurationState.Instance.System.UseHypervisor, - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode); - - _emulationContext = new HLE.Switch(configuration); - } - - private SurfaceKHR CreateVulkanSurface(Instance instance, Vk vk) - { - return new SurfaceKHR((ulong)((VulkanRenderer)RendererWidget).CreateWindowSurface(instance.Handle)); - } - - private void SetupProgressUIHandlers() - { - if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) - { - _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; - _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; - } - - _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; - _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; - } - - private void ProgressHandler<T>(T state, int current, int total) where T : Enum - { - bool visible; - string label; - - switch (state) - { - case LoadState ptcState: - visible = ptcState != LoadState.Loaded; - label = $"PTC : {current}/{total}"; - break; - case ShaderCacheLoadingState shaderCacheState: - visible = shaderCacheState != ShaderCacheLoadingState.Loaded; - label = $"Shaders : {current}/{total}"; - break; - default: - throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); - } - - Application.Invoke(delegate - { - _loadingStatusLabel.Text = label; - _loadingStatusBar.Fraction = total > 0 ? (double)current / total : 0; - _loadingStatusBar.Visible = visible; - _loadingStatusLabel.Visible = visible; - }); - } - - public void UpdateGameTable() - { - if (_updatingGameTable || _gameLoaded) - { - return; - } - - _updatingGameTable = true; - - _tableStore.Clear(); - - Thread applicationLibraryThread = new(() => - { - _applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); - - _updatingGameTable = false; - }) - { - Name = "GUI.ApplicationLibraryThread", - IsBackground = true, - }; - applicationLibraryThread.Start(); - } - - [Conditional("RELEASE")] - public void PerformanceCheck() - { - if (ConfigurationState.Instance.Logger.EnableTrace.Value) - { - MessageDialog debugWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = "You have trace logging enabled, which is designed to be used by developers only.", - SecondaryText = "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", - }; - - if (debugWarningDialog.Run() == (int)ResponseType.Yes) - { - ConfigurationState.Instance.Logger.EnableTrace.Value = false; - SaveConfig(); - } - - debugWarningDialog.Dispose(); - } - - if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) - { - MessageDialog shadersDumpWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = "You have shader dumping enabled, which is designed to be used by developers only.", - SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", - }; - - if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes) - { - ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; - SaveConfig(); - } - - shadersDumpWarningDialog.Dispose(); - } - } - - private bool LoadApplication(string path, bool isFirmwareTitle) - { - SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); - - if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) - { - if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) - { - string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; - - ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); - - if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) - { - UserErrorDialog.CreateUserErrorDialog(userError); - - return false; - } - - // Tell the user that we installed a firmware for them. - - firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); - - RefreshFirmwareLabel(); - - message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; - - GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); - } - else - { - UserErrorDialog.CreateUserErrorDialog(userError); - - return false; - } - } - - Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); - - if (isFirmwareTitle) - { - Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); - - return _emulationContext.LoadNca(path); - } - - if (Directory.Exists(path)) - { - string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); - - if (romFsFiles.Length == 0) - { - romFsFiles = Directory.GetFiles(path, "*.romfs"); - } - - if (romFsFiles.Length > 0) - { - Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); - - return _emulationContext.LoadCart(path, romFsFiles[0]); - } - - Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); - - return _emulationContext.LoadCart(path); - } - - if (File.Exists(path)) - { - switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) - { - case ".xci": - Logger.Info?.Print(LogClass.Application, "Loading as XCI."); - - return _emulationContext.LoadXci(path); - case ".nca": - Logger.Info?.Print(LogClass.Application, "Loading as NCA."); - - return _emulationContext.LoadNca(path); - case ".nsp": - case ".pfs0": - Logger.Info?.Print(LogClass.Application, "Loading as NSP."); - - return _emulationContext.LoadNsp(path); - default: - Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); - try - { - return _emulationContext.LoadProgram(path); - } - catch (ArgumentOutOfRangeException) - { - Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); - - return false; - } - } - } - - Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); - - return false; - } - - public void RunApplication(string path, bool startFullscreen = false) - { - if (_gameLoaded) - { - GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); - } - else - { - PerformanceCheck(); - - Logger.RestartTime(); - - RendererWidget = CreateRendererWidget(); - - SwitchToRenderWidget(startFullscreen); - - InitializeSwitchInstance(); - - UpdateGraphicsConfig(); - - bool isFirmwareTitle = false; - - if (path.StartsWith("@SystemContent")) - { - path = VirtualFileSystem.SwitchPathToSystemPath(path); - - isFirmwareTitle = true; - } - - if (!LoadApplication(path, isFirmwareTitle)) - { - _emulationContext.Dispose(); - SwitchToGameTable(); - - return; - } - - SetupProgressUIHandlers(); - - _currentEmulatedGamePath = path; - - _deviceExitStatus.Reset(); - - Thread windowThread = new(CreateGameWindow) - { - Name = "GUI.WindowThread", - }; - - windowThread.Start(); - - _gameLoaded = true; - _actionMenu.Sensitive = true; - UpdateMenuItem.Sensitive = false; - - _lastScannedAmiiboId = ""; - - _firmwareInstallFile.Sensitive = false; - _firmwareInstallDirectory.Sensitive = false; - - DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText, - _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); - - ApplicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata => - { - appMetadata.UpdatePreGame(); - }); - } - } - - private RendererWidgetBase CreateRendererWidget() - { - if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) - { - return new VulkanRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); - } - else - { - return new OpenGLRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); - } - } - - private void SwitchToRenderWidget(bool startFullscreen = false) - { - _viewBox.Remove(_gameTableWindow); - RendererWidget.Expand = true; - _viewBox.Child = RendererWidget; - - RendererWidget.ShowAll(); - EditFooterForGameRenderer(); - - if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) - { - ToggleExtraWidgets(false); - } - else if (startFullscreen || ConfigurationState.Instance.UI.StartFullscreen.Value) - { - FullScreen_Toggled(null, null); - } - } - - private void SwitchToGameTable() - { - if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) - { - ToggleExtraWidgets(true); - } - - RendererWidget.Exit(); - - if (RendererWidget.Window != Window && RendererWidget.Window != null) - { - RendererWidget.Window.Dispose(); - } - - RendererWidget.Dispose(); - - if (OperatingSystem.IsWindows()) - { - _windowsMultimediaTimerResolution?.Dispose(); - _windowsMultimediaTimerResolution = null; - } - - DisplaySleep.Restore(); - - _viewBox.Remove(RendererWidget); - _viewBox.Add(_gameTableWindow); - - _gameTableWindow.Expand = true; - - Window.Title = $"Ryujinx {Program.Version}"; - - _emulationContext = null; - _gameLoaded = false; - RendererWidget = null; - - DiscordIntegrationModule.SwitchToMainMenu(); - - RecreateFooterForMenu(); - - UpdateColumns(); - UpdateGameTable(); - - RefreshFirmwareLabel(); - HandleRelaunch(); - } - - private void CreateGameWindow() - { - if (OperatingSystem.IsWindows()) - { - _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); - } - - DisplaySleep.Prevent(); - - RendererWidget.Initialize(_emulationContext); - - RendererWidget.WaitEvent.WaitOne(); - - RendererWidget.Start(); - - _emulationContext.Dispose(); - _deviceExitStatus.Set(); - - // NOTE: Everything that is here will not be executed when you close the UI. - Application.Invoke(delegate - { - SwitchToGameTable(); - }); - } - - private void RecreateFooterForMenu() - { - _listStatusBox.Show(); - _statusBar.Hide(); - } - - private void EditFooterForGameRenderer() - { - _listStatusBox.Hide(); - _statusBar.Show(); - } - - public void ToggleExtraWidgets(bool show) - { - if (RendererWidget != null) - { - if (show) - { - _menuBar.ShowAll(); - _footerBox.Show(); - _statusBar.Show(); - } - else - { - _menuBar.Hide(); - _footerBox.Hide(); - } - } - } - - private void UpdateGameMetadata(string titleId) - { - if (_gameLoaded) - { - ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => - { - appMetadata.UpdatePostGame(); - }); - } - } - - public static void UpdateGraphicsConfig() - { - int resScale = ConfigurationState.Instance.Graphics.ResScale; - float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; - - Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale; - Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; - Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; - Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; - Graphics.Gpu.GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; - Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; - } - - public void UpdateInternetAccess() - { - if (_gameLoaded) - { - _emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value; - } - } - - public static void SaveConfig() - { - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - - private void End() - { - if (_ending) - { - return; - } - - _ending = true; - - if (_emulationContext != null) - { - UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); - - if (RendererWidget != null) - { - // We tell the widget that we are exiting. - RendererWidget.Exit(); - - // Wait for the other thread to dispose the HLE context before exiting. - _deviceExitStatus.WaitOne(); - RendererWidget.Dispose(); - } - } - - Dispose(); - - Program.Exit(); - Application.Quit(); - } - - // - // Events - // - private void Application_Added(object sender, ApplicationAddedEventArgs args) - { - Application.Invoke(delegate - { - _tableStore.AppendValues( - args.AppData.Favorite, - new Gdk.Pixbuf(args.AppData.Icon, 75, 75), - $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}", - args.AppData.Developer, - args.AppData.Version, - args.AppData.TimePlayedString, - args.AppData.LastPlayedString, - args.AppData.FileExtension, - args.AppData.FileSizeString, - args.AppData.Path, - args.AppData.ControlHolder); - }); - } - - private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args) - { - Application.Invoke(delegate - { - _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; - float barValue = 0; - - if (args.NumAppsFound != 0) - { - barValue = (float)args.NumAppsLoaded / args.NumAppsFound; - } - - _progressBar.Fraction = barValue; - - // Reset the vertical scrollbar to the top when titles finish loading - if (args.NumAppsLoaded == args.NumAppsFound) - { - _gameTableWindow.Vadjustment.Value = 0; - } - }); - } - - private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) - { - Application.Invoke(delegate - { - _gameStatus.Text = args.GameStatus; - _fifoStatus.Text = args.FifoStatus; - _gpuName.Text = args.GpuName; - _dockedMode.Text = args.DockedMode; - _aspectRatio.Text = args.AspectRatio; - _gpuBackend.Text = args.GpuBackend; - _volumeStatus.Text = GetVolumeLabelText(args.Volume); - - if (args.VSyncEnabled) - { - _vSyncStatus.Attributes = new Pango.AttrList(); - _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(11822, 60138, 51657)); - } - else - { - _vSyncStatus.Attributes = new Pango.AttrList(); - _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(ushort.MaxValue, 17733, 21588)); - } - }); - } - - private void FavToggle_Toggled(object sender, ToggledArgs args) - { - _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); - - string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); - bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); - - _tableStore.SetValue(treeIter, 0, newToggleValue); - - ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => - { - appMetadata.Favorite = newToggleValue; - }); - } - - private void Column_Clicked(object sender, EventArgs args) - { - TreeViewColumn column = (TreeViewColumn)sender; - - ConfigurationState.Instance.UI.ColumnSort.SortColumnId.Value = column.SortColumnId; - ConfigurationState.Instance.UI.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending; - - SaveConfig(); - } - - private void Row_Activated(object sender, RowActivatedArgs args) - { - _gameTableSelection.GetSelected(out TreeIter treeIter); - - string path = (string)_tableStore.GetValue(treeIter, 9); - - RunApplication(path); - } - - private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) - { - _emulationContext.EnableDeviceVsync = !_emulationContext.EnableDeviceVsync; - - Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {_emulationContext.EnableDeviceVsync}"); - } - - private void DockedMode_Clicked(object sender, ButtonReleaseEventArgs args) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; - } - - private static string GetVolumeLabelText(float volume) - { - string icon = volume == 0 ? "🔇" : "🔊"; - - return $"{icon} {(int)(volume * 100)}%"; - } - - private void VolumeStatus_Clicked(object sender, ButtonReleaseEventArgs args) - { - if (_emulationContext != null) - { - if (_emulationContext.IsAudioMuted()) - { - _emulationContext.SetVolume(ConfigurationState.Instance.System.AudioVolume); - } - else - { - _emulationContext.SetVolume(0); - } - } - } - - private void AspectRatio_Clicked(object sender, ButtonReleaseEventArgs args) - { - AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; - - ConfigurationState.Instance.Graphics.AspectRatio.Value = ((int)aspectRatio + 1) > Enum.GetNames<AspectRatio>().Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; - } - - private void Row_Clicked(object sender, ButtonReleaseEventArgs args) - { - if (args.Event.Button != 3 /* Right Click */) - { - return; - } - - _gameTableSelection.GetSelected(out TreeIter treeIter); - - if (treeIter.UserData == IntPtr.Zero) - { - return; - } - - string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString(); - string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0]; - string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); - - BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10); - - _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData); - } - - private void Load_Application_File(object sender, EventArgs args) - { - using FileChooserNative fileChooser = new("Choose the file to open", this, FileChooserAction.Open, "Open", "Cancel"); - - FileFilter filter = new() - { - Name = "Switch Executables", - }; - filter.AddPattern("*.xci"); - filter.AddPattern("*.nsp"); - filter.AddPattern("*.pfs0"); - filter.AddPattern("*.nca"); - filter.AddPattern("*.nro"); - filter.AddPattern("*.nso"); - - fileChooser.AddFilter(filter); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - RunApplication(fileChooser.Filename); - } - } - - private void Load_Application_Folder(object sender, EventArgs args) - { - using FileChooserNative fileChooser = new("Choose the folder to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - RunApplication(fileChooser.Filename); - } - } - - private void FileMenu_StateChanged(object o, StateChangedArgs args) - { - _appletMenu.Sensitive = _emulationContext == null && _contentManager.GetCurrentFirmwareVersion() != null && _contentManager.GetCurrentFirmwareVersion().Major > 3; - _loadApplicationFile.Sensitive = _emulationContext == null; - _loadApplicationFolder.Sensitive = _emulationContext == null; - } - - private void Load_Mii_Edit_Applet(object sender, EventArgs args) - { - string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); - - RunApplication(contentPath); - } - - private void Open_Ryu_Folder(object sender, EventArgs args) - { - OpenHelper.OpenFolder(AppDataManager.BaseDirPath); - } - - private void OpenLogsFolder_Pressed(object sender, EventArgs args) - { - string logPath = AppDataManager.GetOrCreateLogsDir(); - if (!string.IsNullOrEmpty(logPath)) - { - OpenHelper.OpenFolder(logPath); - } - } - - private void Exit_Pressed(object sender, EventArgs args) - { - if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) - { - SaveWindowSizePosition(); - End(); - } - } - - private void Window_Close(object sender, DeleteEventArgs args) - { - if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) - { - SaveWindowSizePosition(); - End(); - } - else - { - args.RetVal = true; - } - } - - private void SetWindowSizePosition() - { - DefaultWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth; - DefaultHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight; - - Move(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); - - if (ConfigurationState.Instance.UI.WindowStartup.WindowMaximized) - { - Maximize(); - } - } - - private void SaveWindowSizePosition() - { - GetSize(out int windowWidth, out int windowHeight); - GetPosition(out int windowXPos, out int windowYPos); - - ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = IsMaximized; - ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = windowWidth; - ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = windowHeight; - ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = windowXPos; - ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = windowYPos; - - SaveConfig(); - } - - private void StopEmulation_Pressed(object sender, EventArgs args) - { - if (_emulationContext != null) - { - UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); - } - - _pauseEmulation.Sensitive = false; - _resumeEmulation.Sensitive = false; - UpdateMenuItem.Sensitive = true; - RendererWidget?.Exit(); - } - - private void PauseEmulation_Pressed(object sender, EventArgs args) - { - _pauseEmulation.Sensitive = false; - _resumeEmulation.Sensitive = true; - _emulationContext.System.TogglePauseEmulation(true); - Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version, "Paused"); - Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); - } - - private void ResumeEmulation_Pressed(object sender, EventArgs args) - { - _pauseEmulation.Sensitive = true; - _resumeEmulation.Sensitive = false; - _emulationContext.System.TogglePauseEmulation(false); - Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version); - Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); - } - - public void ActivatePauseMenu() - { - _pauseEmulation.Sensitive = true; - _resumeEmulation.Sensitive = false; - } - - public void TogglePause() - { - _pauseEmulation.Sensitive ^= true; - _resumeEmulation.Sensitive ^= true; - _emulationContext.System.TogglePauseEmulation(_resumeEmulation.Sensitive); - } - - private void Installer_File_Pressed(object o, EventArgs args) - { - FileChooserNative fileChooser = new("Choose the firmware file to open", this, FileChooserAction.Open, "Open", "Cancel"); - - FileFilter filter = new() - { - Name = "Switch Firmware Files", - }; - filter.AddPattern("*.zip"); - filter.AddPattern("*.xci"); - - fileChooser.AddFilter(filter); - - HandleInstallerDialog(fileChooser); - } - - private void Installer_Directory_Pressed(object o, EventArgs args) - { - FileChooserNative directoryChooser = new("Choose the firmware directory to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); - - HandleInstallerDialog(directoryChooser); - } - - private void HandleInstallerDialog(FileChooserNative fileChooser) - { - if (fileChooser.Run() == (int)ResponseType.Accept) - { - try - { - string filename = fileChooser.Filename; - - fileChooser.Dispose(); - - SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); - - if (firmwareVersion is null) - { - GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); - - return; - } - - string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; - - SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion(); - - string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; - - if (currentVersion != null) - { - dialogMessage += $"\n\nThis will replace the current system version {currentVersion.VersionString}. "; - } - - dialogMessage += "\n\nDo you want to continue?"; - - ResponseType responseInstallDialog = (ResponseType)GtkDialog.CreateConfirmationDialog(dialogTitle, dialogMessage).Run(); - - MessageDialog waitingDialog = GtkDialog.CreateWaitingDialog(dialogTitle, "Installing firmware..."); - - if (responseInstallDialog == ResponseType.Yes) - { - Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); - - Thread thread = new(() => - { - Application.Invoke(delegate - { - waitingDialog.Run(); - - }); - - try - { - _contentManager.InstallFirmware(filename); - - Application.Invoke(delegate - { - waitingDialog.Dispose(); - - string message = $"System version {firmwareVersion.VersionString} successfully installed."; - - GtkDialog.CreateInfoDialog(dialogTitle, message); - Logger.Info?.Print(LogClass.Application, message); - - // Purge Applet Cache. - - DirectoryInfo miiEditorCacheFolder = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); - - if (miiEditorCacheFolder.Exists) - { - miiEditorCacheFolder.Delete(true); - } - }); - } - catch (Exception ex) - { - Application.Invoke(delegate - { - waitingDialog.Dispose(); - - GtkDialog.CreateErrorDialog(ex.Message); - }); - } - finally - { - RefreshFirmwareLabel(); - } - }) - { - Name = "GUI.FirmwareInstallerThread", - }; - thread.Start(); - } - } - catch (MissingKeyException ex) - { - Logger.Error?.Print(LogClass.Application, ex.ToString()); - UserErrorDialog.CreateUserErrorDialog(UserError.FirmwareParsingFailed); - } - catch (Exception ex) - { - GtkDialog.CreateErrorDialog(ex.Message); - } - } - else - { - fileChooser.Dispose(); - } - } - - private void RefreshFirmwareLabel() - { - SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion(); - - Application.Invoke(delegate - { - _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; - }); - } - - private void InstallFileTypes_Pressed(object sender, EventArgs e) - { - if (FileAssociationHelper.Install()) - { - GtkDialog.CreateInfoDialog("Install file types", "File types successfully installed!"); - } - else - { - GtkDialog.CreateErrorDialog("Failed to install file types."); - } - } - - private void UninstallFileTypes_Pressed(object sender, EventArgs e) - { - if (FileAssociationHelper.Uninstall()) - { - GtkDialog.CreateInfoDialog("Uninstall file types", "File types successfully uninstalled!"); - } - else - { - GtkDialog.CreateErrorDialog("Failed to uninstall file types."); - } - } - - private void HandleRelaunch() - { - if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) - { - _userChannelPersistence.ShouldRestart = false; - - RunApplication(_currentEmulatedGamePath); - } - else - { - // otherwise, clear state. - _userChannelPersistence = new UserChannelPersistence(); - _currentEmulatedGamePath = null; - _actionMenu.Sensitive = false; - _firmwareInstallFile.Sensitive = true; - _firmwareInstallDirectory.Sensitive = true; - } - } - - private void FullScreen_Toggled(object sender, EventArgs args) - { - if (!Window.State.HasFlag(Gdk.WindowState.Fullscreen)) - { - Fullscreen(); - - ToggleExtraWidgets(false); - } - else - { - Unfullscreen(); - - ToggleExtraWidgets(true); - } - } - - private void StartFullScreen_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.StartFullscreen.Value = _startFullScreen.Active; - - SaveConfig(); - } - - private void ShowConsole_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShowConsole.Value = _showConsole.Active; - - SaveConfig(); - } - - private void OptionMenu_StateChanged(object o, StateChangedArgs args) - { - _manageUserProfiles.Sensitive = _emulationContext == null; - } - - private void Settings_Pressed(object sender, EventArgs args) - { - SettingsWindow settingsWindow = new(this, _virtualFileSystem, _contentManager); - - settingsWindow.SetSizeRequest((int)(settingsWindow.DefaultWidth * Program.WindowScaleFactor), (int)(settingsWindow.DefaultHeight * Program.WindowScaleFactor)); - settingsWindow.Show(); - } - - private void HideUI_Pressed(object sender, EventArgs args) - { - ToggleExtraWidgets(false); - } - - private void ManageCheats_Pressed(object sender, EventArgs args) - { - var window = new CheatWindow( - _virtualFileSystem, - _emulationContext.Processes.ActiveApplication.ProgramId, - _emulationContext.Processes.ActiveApplication.ApplicationControlProperties - .Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(), - _currentEmulatedGamePath); - - window.Destroyed += CheatWindow_Destroyed; - window.Show(); - } - - private void CheatWindow_Destroyed(object sender, EventArgs e) - { - _emulationContext.EnableCheats(); - (sender as CheatWindow).Destroyed -= CheatWindow_Destroyed; - } - - private void ManageUserProfiles_Pressed(object sender, EventArgs args) - { - UserProfilesManagerWindow userProfilesManagerWindow = new(_accountManager, _contentManager, _virtualFileSystem); - - userProfilesManagerWindow.SetSizeRequest((int)(userProfilesManagerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(userProfilesManagerWindow.DefaultHeight * Program.WindowScaleFactor)); - userProfilesManagerWindow.Show(); - } - - private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args) - { - _emulationContext?.System.SimulateWakeUpMessage(); - } - - private void ActionMenu_StateChanged(object o, StateChangedArgs args) - { - _scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _); - _takeScreenshot.Sensitive = _emulationContext != null; - } - - private void Scan_Amiibo(object sender, EventArgs args) - { - if (_emulationContext.System.SearchingForAmiibo(out int deviceId)) - { - AmiiboWindow amiiboWindow = new() - { - LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, - LastScannedAmiiboId = _lastScannedAmiiboId, - DeviceId = deviceId, - TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper(), - }; - - amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; - - amiiboWindow.Show(); - } - else - { - GtkDialog.CreateInfoDialog($"Amiibo", "The game is currently not ready to receive Amiibo scan data. Ensure that you have an Amiibo-compatible game open and ready to receive Amiibo scan data."); - } - } - - private void Take_Screenshot(object sender, EventArgs args) - { - if (_emulationContext != null && RendererWidget != null) - { - RendererWidget.ScreenshotRequested = true; - } - } - - private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args) - { - if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok) - { - _lastScannedAmiiboId = ((AmiiboWindow)sender).AmiiboId; - _lastScannedAmiiboShowAll = ((AmiiboWindow)sender).LastScannedAmiiboShowAll; - - _emulationContext.System.ScanAmiibo(((AmiiboWindow)sender).DeviceId, ((AmiiboWindow)sender).AmiiboId, ((AmiiboWindow)sender).UseRandomUuid); - } - } - - private void Update_Pressed(object sender, EventArgs args) - { - if (Updater.CanUpdate(true)) - { - Updater.BeginParse(this, true).ContinueWith(task => - { - Logger.Error?.Print(LogClass.Application, $"Updater error: {task.Exception}"); - }, TaskContinuationOptions.OnlyOnFaulted); - } - } - - private void About_Pressed(object sender, EventArgs args) - { - AboutWindow aboutWindow = new(); - - aboutWindow.SetSizeRequest((int)(aboutWindow.DefaultWidth * Program.WindowScaleFactor), (int)(aboutWindow.DefaultHeight * Program.WindowScaleFactor)); - aboutWindow.Show(); - } - - private void Fav_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.FavColumn.Value = _favToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void Icon_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.IconColumn.Value = _iconToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void App_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.AppColumn.Value = _appToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void Developer_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.DevColumn.Value = _developerToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void Version_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.VersionColumn.Value = _versionToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void TimePlayed_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void LastPlayed_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void FileExt_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void FileSize_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void Path_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.GuiColumns.PathColumn.Value = _pathToggle.Active; - - SaveConfig(); - UpdateColumns(); - } - - private void NSP_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = _nspShown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void PFS0_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = _pfs0Shown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void XCI_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = _xciShown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void NCA_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = _ncaShown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void NRO_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = _nroShown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void NSO_Shown_Toggled(object sender, EventArgs args) - { - ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = _nsoShown.Active; - - SaveConfig(); - UpdateGameTable(); - } - - private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args) - { - UpdateGameTable(); - } - } -} diff --git a/src/Ryujinx/UI/MainWindow.glade b/src/Ryujinx/UI/MainWindow.glade deleted file mode 100644 index d1b6872a..00000000 --- a/src/Ryujinx/UI/MainWindow.glade +++ /dev/null @@ -1,1006 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.40.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkApplicationWindow" id="_mainWin"> - <property name="can-focus">False</property> - <property name="title" translatable="yes">Ryujinx</property> - <property name="window-position">center</property> - <child> - <object class="GtkBox" id="_box"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkMenuBar" id="_menuBar"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_fileMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">File</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_loadApplicationFile"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open a file explorer to choose a Switch compatible file to load</property> - <property name="label" translatable="yes">Load Application from File</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Load_Application_File" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_loadApplicationFolder"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open a file explorer to choose a Switch compatible, unpacked application to load</property> - <property name="label" translatable="yes">Load Unpacked Game</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Load_Application_Folder" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_appletMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Load Applet</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="LoadMiiEditApplet"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open Mii Editor Applet in Standalone mode</property> - <property name="label" translatable="yes">Mii Editor</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Load_Mii_Edit_Applet" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="OpenRyuFolder"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open Ryujinx filesystem folder</property> - <property name="label" translatable="yes">Open Ryujinx Folder</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Open_Ryu_Folder" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="OpenLogsFolder"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Opens the folder where logs are written to.</property> - <property name="label" translatable="yes">Open Logs Folder</property> - <property name="use-underline">True</property> - <signal name="activate" handler="OpenLogsFolder_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="ExitMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Exit Ryujinx</property> - <property name="label" translatable="yes">Exit</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Exit_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_optionMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Options</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_fullScreen"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Enter Fullscreen</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_startFullScreen"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Start Games in Fullscreen Mode</property> - <property name="use-underline">True</property> - <signal name="toggled" handler="StartFullScreen_Toggled" swapped="no"/> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_showConsole"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Show Log Console</property> - <property name="use-underline">True</property> - <signal name="toggled" handler="ShowConsole_Toggled" swapped="no"/> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="GUIColumns"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Select which GUI columns to enable</property> - <property name="label" translatable="yes">Enable GUI Columns</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkCheckMenuItem" id="_favToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Favorite Games Column in the game list</property> - <property name="label" translatable="yes">Enable Favorite Games Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_iconToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Icon Column in the game list</property> - <property name="label" translatable="yes">Enable Icon Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_appToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Title Name/ID Column in the game list</property> - <property name="label" translatable="yes">Enable Title Name/ID Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_developerToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Developer Column in the game list</property> - <property name="label" translatable="yes">Enable Developer Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_versionToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Version Column in the game list</property> - <property name="label" translatable="yes">Enable Version Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_timePlayedToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Time Played Column in the game list</property> - <property name="label" translatable="yes">Enable Time Played Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_lastPlayedToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Last Played Column in the game list</property> - <property name="label" translatable="yes">Enable Last Played Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_fileExtToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable file extension column in the game list</property> - <property name="label" translatable="yes">Enable File Ext Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_fileSizeToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable File Size Column in the game list</property> - <property name="label" translatable="yes">Enable File Size Column</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_pathToggle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enable or Disable Path Column in the game list</property> - <property name="label" translatable="yes">Enable Path Column</property> - <property name="use-underline">True</property> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="ShownFileTypes"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Select which file types to show</property> - <property name="label" translatable="yes">Show File Types</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkCheckMenuItem" id="_nspShown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .NSP files in the games list</property> - <property name="label" translatable="yes">.NSP</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_pfs0Shown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .PFS0 files in the games list</property> - <property name="label" translatable="yes">.PFS0</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_xciShown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .XCI files in the games list</property> - <property name="label" translatable="yes">.XCI</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_ncaShown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .NCA files in the games list</property> - <property name="label" translatable="yes">.NCA</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_nroShown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .NRO files in the games list</property> - <property name="label" translatable="yes">.NRO</property> - <property name="use-underline">True</property> - </object> - </child> - <child> - <object class="GtkCheckMenuItem" id="_nsoShown"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Shows .NSO files in the games list</property> - <property name="label" translatable="yes">.NSO</property> - <property name="use-underline">True</property> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="SettingsMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open settings window</property> - <property name="label" translatable="yes">Settings</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Settings_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_manageUserProfiles"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open User Profiles Manager window</property> - <property name="label" translatable="yes">Manage User Profiles</property> - <property name="use-underline">True</property> - <signal name="activate" handler="ManageUserProfiles_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_actionMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Actions</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_pauseEmulation"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Pause emulation</property> - <property name="label" translatable="yes">Pause Emulation</property> - <property name="use-underline">True</property> - <signal name="activate" handler="PauseEmulation_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_resumeEmulation"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Resume emulation</property> - <property name="label" translatable="yes">Resume Emulation</property> - <property name="use-underline">True</property> - <signal name="activate" handler="ResumeEmulation_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_stopEmulation"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Stop emulation of the current game and return to game selection</property> - <property name="label" translatable="yes">Stop Emulation</property> - <property name="use-underline">True</property> - <signal name="activate" handler="StopEmulation_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_simulateWakeUpMessage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Simulate a Wake-up Message</property> - <property name="label" translatable="yes">Simulate Wake-up Message</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Simulate_WakeUp_Message_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_scanAmiibo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Scan an Amiibo</property> - <property name="label" translatable="yes">Scan an Amiibo</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Scan_Amiibo" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_takeScreenshot"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Take a screenshot</property> - <property name="label" translatable="yes">Take Screenshot</property> - <signal name="activate" handler="Take_Screenshot" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_hideUI"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Hide UI (SHOWUIKEY to show)</property> - <property name="use-underline">True</property> - <signal name="activate" handler="HideUI_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_manageCheats"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Manage Cheats</property> - <signal name="activate" handler="ManageCheats_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_toolsMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Tools</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="FirmwareSubMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Install Firmware</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_firmwareInstallFile"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Install a firmware from XCI or ZIP</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Installer_File_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_firmwareInstallDirectory"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Install a firmware from a directory</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Installer_Directory_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_fileTypesSubMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Manage file types</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="_installFileTypes"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Install file types</property> - <signal name="activate" handler="InstallFileTypes_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkMenuItem" id="_uninstallFileTypes"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Uninstall file types</property> - <signal name="activate" handler="UninstallFileTypes_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - </object> - </child> - </object> - </child> - <child> - <object class="GtkMenuItem" id="HelpMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Help</property> - <property name="use-underline">True</property> - <child type="submenu"> - <object class="GtkMenu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkMenuItem" id="UpdateMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Check for updates to Ryujinx</property> - <property name="label" translatable="yes">Check for Updates</property> - <property name="use-underline">True</property> - <signal name="activate" handler="Update_Pressed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkSeparatorMenuItem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - </child> - <child> - <object class="GtkMenuItem" id="About"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Open about window</property> - <property name="label" translatable="yes">About</property> - <property name="use-underline">True</property> - <signal name="activate" handler="About_Pressed" swapped="no"/> - </object> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_mainBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="_viewBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkScrolledWindow" id="_gameTableWindow"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="shadow-type">in</property> - <child> - <object class="GtkTreeView" id="_gameTable"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="reorderable">True</property> - <property name="hover-selection">True</property> - <signal name="row-activated" handler="Row_Activated" swapped="no"/> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="_gameTableSelection"/> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_footerBox"> - <property name="height-request">19</property> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkBox" id="_listStatusBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkEventBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/> - <child> - <object class="GtkImage"> - <property name="name">RefreshList</property> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="stock">gtk-refresh</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_progressLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">5</property> - <property name="margin-top">2</property> - <property name="margin-bottom">2</property> - <property name="label" translatable="yes">0/0 Games Loaded</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkProgressBar" id="_progressBar"> - <property name="width-request">200</property> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">10</property> - <property name="margin-right">5</property> - <property name="margin-bottom">6</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_statusBar"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkEventBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <signal name="button-release-event" handler="VSyncStatus_Clicked" swapped="no"/> - <child> - <object class="GtkLabel" id="_vSyncStatus"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="label" translatable="yes">VSync</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkEventBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <signal name="button-release-event" handler="DockedMode_Clicked" swapped="no"/> - <child> - <object class="GtkLabel" id="_dockedMode"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkEventBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <signal name="button-release-event" handler="VolumeStatus_Clicked" swapped="no"/> - <child> - <object class="GtkLabel" id="_volumeStatus"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkEventBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <signal name="button-release-event" handler="AspectRatio_Clicked" swapped="no"/> - <child> - <object class="GtkLabel" id="_aspectRatio"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">7</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_gameStatus"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">8</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">9</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_fifoStatus"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">10</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">11</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_gpuBackend"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">12</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">13</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_gpuName"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">14</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">System Version</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_firmwareVersionLabel"> - <property name="width-request">50</property> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack-type">end</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack-type">end</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_loadingStatusLabel"> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="label" translatable="yes">0/0 </property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">11</property> - </packing> - </child> - <child> - <object class="GtkProgressBar" id="_loadingStatusBar"> - <property name="width-request">200</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="margin-bottom">6</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">12</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Models/CheatNode.cs b/src/Ryujinx/UI/Models/CheatNode.cs new file mode 100644 index 00000000..8e9aee25 --- /dev/null +++ b/src/Ryujinx/UI/Models/CheatNode.cs @@ -0,0 +1,57 @@ +using Ryujinx.Ava.UI.ViewModels; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Ryujinx.Ava.UI.Models +{ + public class CheatNode : BaseModel + { + private bool _isEnabled = false; + public ObservableCollection<CheatNode> SubNodes { get; } = new(); + public string CleanName => Name[1..^7]; + public string BuildIdKey => $"{BuildId}-{Name}"; + public bool IsRootNode { get; } + public string Name { get; } + public string BuildId { get; } + public string Path { get; } + public bool IsEnabled + { + get + { + if (SubNodes.Count > 0) + { + return SubNodes.ToList().TrueForAll(x => x.IsEnabled); + } + + return _isEnabled; + } + set + { + foreach (var cheat in SubNodes) + { + cheat.IsEnabled = value; + cheat.OnPropertyChanged(); + } + + _isEnabled = value; + } + } + + public CheatNode(string name, string buildId, string path, bool isRootNode, bool isEnabled = false) + { + Name = name; + BuildId = buildId; + Path = path; + IsEnabled = isEnabled; + IsRootNode = isRootNode; + + SubNodes.CollectionChanged += CheatsList_CollectionChanged; + } + + private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(IsEnabled)); + } + } +} diff --git a/src/Ryujinx/UI/Models/ControllerModel.cs b/src/Ryujinx/UI/Models/ControllerModel.cs new file mode 100644 index 00000000..98de7757 --- /dev/null +++ b/src/Ryujinx/UI/Models/ControllerModel.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models +{ + internal record ControllerModel(ControllerType Type, string Name); +} diff --git a/src/Ryujinx/UI/Models/DeviceType.cs b/src/Ryujinx/UI/Models/DeviceType.cs new file mode 100644 index 00000000..bb4fc3b3 --- /dev/null +++ b/src/Ryujinx/UI/Models/DeviceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ava.UI.Models +{ + public enum DeviceType + { + None, + Keyboard, + Controller, + } +} diff --git a/src/Ryujinx/UI/Models/DownloadableContentModel.cs b/src/Ryujinx/UI/Models/DownloadableContentModel.cs new file mode 100644 index 00000000..9e400441 --- /dev/null +++ b/src/Ryujinx/UI/Models/DownloadableContentModel.cs @@ -0,0 +1,35 @@ +using Ryujinx.Ava.UI.ViewModels; +using System.IO; + +namespace Ryujinx.Ava.UI.Models +{ + public class DownloadableContentModel : BaseModel + { + private bool _enabled; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + + OnPropertyChanged(); + } + } + + public string TitleId { get; } + public string ContainerPath { get; } + public string FullPath { get; } + + public string FileName => Path.GetFileName(ContainerPath); + + public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled) + { + TitleId = titleId; + ContainerPath = containerPath; + FullPath = fullPath; + Enabled = enabled; + } + } +} diff --git a/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs new file mode 100644 index 00000000..224f78f4 --- /dev/null +++ b/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs @@ -0,0 +1,31 @@ +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Models.Generic +{ + internal class LastPlayedSortComparer : IComparer<ApplicationData> + { + public LastPlayedSortComparer() { } + public LastPlayedSortComparer(bool isAscending) { IsAscending = isAscending; } + + public bool IsAscending { get; } + + public int Compare(ApplicationData x, ApplicationData y) + { + DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch; + + if (x?.LastPlayed != null) + { + aValue = x.LastPlayed.Value; + } + + if (y?.LastPlayed != null) + { + bValue = y.LastPlayed.Value; + } + + return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue); + } + } +} diff --git a/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs new file mode 100644 index 00000000..f0fb035d --- /dev/null +++ b/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs @@ -0,0 +1,31 @@ +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Models.Generic +{ + internal class TimePlayedSortComparer : IComparer<ApplicationData> + { + public TimePlayedSortComparer() { } + public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; } + + public bool IsAscending { get; } + + public int Compare(ApplicationData x, ApplicationData y) + { + TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero; + + if (x?.TimePlayed != null) + { + aValue = x.TimePlayed; + } + + if (y?.TimePlayed != null) + { + bValue = y.TimePlayed; + } + + return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue); + } + } +} diff --git a/src/Ryujinx/UI/Models/InputConfiguration.cs b/src/Ryujinx/UI/Models/InputConfiguration.cs new file mode 100644 index 00000000..f1352c6d --- /dev/null +++ b/src/Ryujinx/UI/Models/InputConfiguration.cs @@ -0,0 +1,456 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class InputConfiguration<TKey, TStick> : BaseModel + { + private float _deadzoneRight; + private float _triggerThreshold; + private float _deadzoneLeft; + private double _gyroDeadzone; + private int _sensitivity; + private bool _enableMotion; + private float _weakRumble; + private float _strongRumble; + private float _rangeLeft; + private float _rangeRight; + + public InputBackendType Backend { get; set; } + + /// <summary> + /// Controller id + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Controller's Type + /// </summary> + public ControllerType ControllerType { get; set; } + + /// <summary> + /// Player's Index for the controller + /// </summary> + public PlayerIndex PlayerIndex { get; set; } + + public TStick LeftJoystick { get; set; } + public bool LeftInvertStickX { get; set; } + public bool LeftInvertStickY { get; set; } + public bool RightRotate90 { get; set; } + public TKey LeftControllerStickButton { get; set; } + + public TStick RightJoystick { get; set; } + public bool RightInvertStickX { get; set; } + public bool RightInvertStickY { get; set; } + public bool LeftRotate90 { get; set; } + public TKey RightControllerStickButton { get; set; } + + public float DeadzoneLeft + { + get => _deadzoneLeft; + set + { + _deadzoneLeft = MathF.Round(value, 3); + + OnPropertyChanged(); + } + } + + public float RangeLeft + { + get => _rangeLeft; + set + { + _rangeLeft = MathF.Round(value, 3); + + OnPropertyChanged(); + } + } + + public float DeadzoneRight + { + get => _deadzoneRight; + set + { + _deadzoneRight = MathF.Round(value, 3); + + OnPropertyChanged(); + } + } + + public float RangeRight + { + get => _rangeRight; + set + { + _rangeRight = MathF.Round(value, 3); + + OnPropertyChanged(); + } + } + + public float TriggerThreshold + { + get => _triggerThreshold; + set + { + _triggerThreshold = MathF.Round(value, 3); + + OnPropertyChanged(); + } + } + + public MotionInputBackendType MotionBackend { get; set; } + + public TKey ButtonMinus { get; set; } + public TKey ButtonL { get; set; } + public TKey ButtonZl { get; set; } + public TKey LeftButtonSl { get; set; } + public TKey LeftButtonSr { get; set; } + public TKey DpadUp { get; set; } + public TKey DpadDown { get; set; } + public TKey DpadLeft { get; set; } + public TKey DpadRight { get; set; } + + public TKey ButtonPlus { get; set; } + public TKey ButtonR { get; set; } + public TKey ButtonZr { get; set; } + public TKey RightButtonSl { get; set; } + public TKey RightButtonSr { get; set; } + public TKey ButtonX { get; set; } + public TKey ButtonB { get; set; } + public TKey ButtonY { get; set; } + public TKey ButtonA { get; set; } + + public TKey LeftStickUp { get; set; } + public TKey LeftStickDown { get; set; } + public TKey LeftStickLeft { get; set; } + public TKey LeftStickRight { get; set; } + public TKey LeftKeyboardStickButton { get; set; } + + public TKey RightStickUp { get; set; } + public TKey RightStickDown { get; set; } + public TKey RightStickLeft { get; set; } + public TKey RightStickRight { get; set; } + public TKey RightKeyboardStickButton { get; set; } + + public int Sensitivity + { + get => _sensitivity; + set + { + _sensitivity = value; + + OnPropertyChanged(); + } + } + + public double GyroDeadzone + { + get => _gyroDeadzone; + set + { + _gyroDeadzone = Math.Round(value, 3); + + OnPropertyChanged(); + } + } + + public bool EnableMotion + { + get => _enableMotion; set + { + _enableMotion = value; + + OnPropertyChanged(); + } + } + + public bool EnableCemuHookMotion { get; set; } + public int Slot { get; set; } + public int AltSlot { get; set; } + public bool MirrorInput { get; set; } + public string DsuServerHost { get; set; } + public int DsuServerPort { get; set; } + + public bool EnableRumble { get; set; } + public float WeakRumble + { + get => _weakRumble; set + { + _weakRumble = value; + + OnPropertyChanged(); + } + } + public float StrongRumble + { + get => _strongRumble; set + { + _strongRumble = value; + + OnPropertyChanged(); + } + } + + public InputConfiguration(InputConfig config) + { + if (config != null) + { + Backend = config.Backend; + Id = config.Id; + ControllerType = config.ControllerType; + PlayerIndex = config.PlayerIndex; + + if (config is StandardKeyboardInputConfig keyboardConfig) + { + LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp; + LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown; + LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft; + LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight; + LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton; + + RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp; + RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown; + RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft; + RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight; + RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton; + + ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA; + ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB; + ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX; + ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY; + ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR; + RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl; + RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr; + ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr; + ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus; + + DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp; + DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown; + DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft; + DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight; + ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus; + LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl; + LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr; + ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl; + ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL; + } + else if (config is StandardControllerInputConfig controllerConfig) + { + LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick; + LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX; + LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY; + LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW; + LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton; + + RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick; + RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX; + RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY; + RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW; + RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton; + + ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA; + ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB; + ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX; + ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY; + ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR; + RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl; + RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr; + ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr; + ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus; + + DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp; + DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown; + DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft; + DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight; + ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus; + LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl; + LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr; + ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl; + ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL; + + DeadzoneLeft = controllerConfig.DeadzoneLeft; + DeadzoneRight = controllerConfig.DeadzoneRight; + RangeLeft = controllerConfig.RangeLeft; + RangeRight = controllerConfig.RangeRight; + TriggerThreshold = controllerConfig.TriggerThreshold; + + if (controllerConfig.Motion != null) + { + EnableMotion = controllerConfig.Motion.EnableMotion; + MotionBackend = controllerConfig.Motion.MotionBackend; + GyroDeadzone = controllerConfig.Motion.GyroDeadzone; + Sensitivity = controllerConfig.Motion.Sensitivity; + + if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook) + { + EnableCemuHookMotion = true; + DsuServerHost = cemuHook.DsuServerHost; + DsuServerPort = cemuHook.DsuServerPort; + Slot = cemuHook.Slot; + AltSlot = cemuHook.AltSlot; + MirrorInput = cemuHook.MirrorInput; + } + + if (controllerConfig.Rumble != null) + { + EnableRumble = controllerConfig.Rumble.EnableRumble; + WeakRumble = controllerConfig.Rumble.WeakRumble; + StrongRumble = controllerConfig.Rumble.StrongRumble; + } + } + } + } + } + + public InputConfiguration() + { + } + + public InputConfig GetConfig() + { + if (Backend == InputBackendType.WindowKeyboard) + { + return new StandardKeyboardInputConfig + { + Id = Id, + Backend = Backend, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig<Key> + { + DpadUp = (Key)(object)DpadUp, + DpadDown = (Key)(object)DpadDown, + DpadLeft = (Key)(object)DpadLeft, + DpadRight = (Key)(object)DpadRight, + ButtonL = (Key)(object)ButtonL, + ButtonZl = (Key)(object)ButtonZl, + ButtonSl = (Key)(object)LeftButtonSl, + ButtonSr = (Key)(object)LeftButtonSr, + ButtonMinus = (Key)(object)ButtonMinus, + }, + RightJoycon = new RightJoyconCommonConfig<Key> + { + ButtonA = (Key)(object)ButtonA, + ButtonB = (Key)(object)ButtonB, + ButtonX = (Key)(object)ButtonX, + ButtonY = (Key)(object)ButtonY, + ButtonPlus = (Key)(object)ButtonPlus, + ButtonSl = (Key)(object)RightButtonSl, + ButtonSr = (Key)(object)RightButtonSr, + ButtonR = (Key)(object)ButtonR, + ButtonZr = (Key)(object)ButtonZr, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick<Key> + { + StickUp = (Key)(object)LeftStickUp, + StickDown = (Key)(object)LeftStickDown, + StickRight = (Key)(object)LeftStickRight, + StickLeft = (Key)(object)LeftStickLeft, + StickButton = (Key)(object)LeftKeyboardStickButton, + }, + RightJoyconStick = new JoyconConfigKeyboardStick<Key> + { + StickUp = (Key)(object)RightStickUp, + StickDown = (Key)(object)RightStickDown, + StickLeft = (Key)(object)RightStickLeft, + StickRight = (Key)(object)RightStickRight, + StickButton = (Key)(object)RightKeyboardStickButton, + }, + Version = InputConfig.CurrentVersion, + }; + + } + + if (Backend == InputBackendType.GamepadSDL2) + { + var config = new StandardControllerInputConfig + { + Id = Id, + Backend = Backend, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId> + { + DpadUp = (GamepadInputId)(object)DpadUp, + DpadDown = (GamepadInputId)(object)DpadDown, + DpadLeft = (GamepadInputId)(object)DpadLeft, + DpadRight = (GamepadInputId)(object)DpadRight, + ButtonL = (GamepadInputId)(object)ButtonL, + ButtonZl = (GamepadInputId)(object)ButtonZl, + ButtonSl = (GamepadInputId)(object)LeftButtonSl, + ButtonSr = (GamepadInputId)(object)LeftButtonSr, + ButtonMinus = (GamepadInputId)(object)ButtonMinus, + }, + RightJoycon = new RightJoyconCommonConfig<GamepadInputId> + { + ButtonA = (GamepadInputId)(object)ButtonA, + ButtonB = (GamepadInputId)(object)ButtonB, + ButtonX = (GamepadInputId)(object)ButtonX, + ButtonY = (GamepadInputId)(object)ButtonY, + ButtonPlus = (GamepadInputId)(object)ButtonPlus, + ButtonSl = (GamepadInputId)(object)RightButtonSl, + ButtonSr = (GamepadInputId)(object)RightButtonSr, + ButtonR = (GamepadInputId)(object)ButtonR, + ButtonZr = (GamepadInputId)(object)ButtonZr, + }, + LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId> + { + Joystick = (StickInputId)(object)LeftJoystick, + InvertStickX = LeftInvertStickX, + InvertStickY = LeftInvertStickY, + Rotate90CW = LeftRotate90, + StickButton = (GamepadInputId)(object)LeftControllerStickButton, + }, + RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId> + { + Joystick = (StickInputId)(object)RightJoystick, + InvertStickX = RightInvertStickX, + InvertStickY = RightInvertStickY, + Rotate90CW = RightRotate90, + StickButton = (GamepadInputId)(object)RightControllerStickButton, + }, + Rumble = new RumbleConfigController + { + EnableRumble = EnableRumble, + WeakRumble = WeakRumble, + StrongRumble = StrongRumble, + }, + Version = InputConfig.CurrentVersion, + DeadzoneLeft = DeadzoneLeft, + DeadzoneRight = DeadzoneRight, + RangeLeft = RangeLeft, + RangeRight = RangeRight, + TriggerThreshold = TriggerThreshold, + Motion = EnableCemuHookMotion + ? new CemuHookMotionConfigController + { + DsuServerHost = DsuServerHost, + DsuServerPort = DsuServerPort, + Slot = Slot, + AltSlot = AltSlot, + MirrorInput = MirrorInput, + MotionBackend = MotionInputBackendType.CemuHook, + } + : new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + }, + }; + + config.Motion.Sensitivity = Sensitivity; + config.Motion.EnableMotion = EnableMotion; + config.Motion.GyroDeadzone = GyroDeadzone; + + return config; + } + + return null; + } + } +} diff --git a/src/Ryujinx/UI/Models/ModModel.cs b/src/Ryujinx/UI/Models/ModModel.cs new file mode 100644 index 00000000..ee28ca5f --- /dev/null +++ b/src/Ryujinx/UI/Models/ModModel.cs @@ -0,0 +1,32 @@ +using Ryujinx.Ava.UI.ViewModels; +using System.IO; + +namespace Ryujinx.Ava.UI.Models +{ + public class ModModel : BaseModel + { + private bool _enabled; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + OnPropertyChanged(); + } + } + + public bool InSd { get; } + public string Path { get; } + public string Name { get; } + + public ModModel(string path, string name, bool enabled, bool inSd) + { + Path = path; + Name = name; + Enabled = enabled; + InSd = inSd; + } + } +} diff --git a/src/Ryujinx/UI/Models/PlayerModel.cs b/src/Ryujinx/UI/Models/PlayerModel.cs new file mode 100644 index 00000000..a19852b9 --- /dev/null +++ b/src/Ryujinx/UI/Models/PlayerModel.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models +{ + public record PlayerModel(PlayerIndex Id, string Name); +} diff --git a/src/Ryujinx/UI/Models/ProfileImageModel.cs b/src/Ryujinx/UI/Models/ProfileImageModel.cs new file mode 100644 index 00000000..99365dfc --- /dev/null +++ b/src/Ryujinx/UI/Models/ProfileImageModel.cs @@ -0,0 +1,32 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.ViewModels; + +namespace Ryujinx.Ava.UI.Models +{ + public class ProfileImageModel : BaseModel + { + public ProfileImageModel(string name, byte[] data) + { + Name = name; + Data = data; + } + + public string Name { get; set; } + public byte[] Data { get; set; } + + private SolidColorBrush _backgroundColor = new(Colors.White); + + public SolidColorBrush BackgroundColor + { + get + { + return _backgroundColor; + } + set + { + _backgroundColor = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs new file mode 100644 index 00000000..d6dea2f6 --- /dev/null +++ b/src/Ryujinx/UI/Models/SaveModel.cs @@ -0,0 +1,96 @@ +using LibHac.Fs; +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 System.IO; +using System.Linq; +using System.Threading.Tasks; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.Models +{ + public class SaveModel : BaseModel + { + private long _size; + + public ulong SaveId { get; } + public ProgramId TitleId { get; } + public string TitleIdString => $"{TitleId.Value:X16}"; + public UserId UserId { get; } + public bool InGameList { get; } + public string Title { get; } + public byte[] Icon { get; } + + public long Size + { + get => _size; set + { + _size = value; + SizeAvailable = true; + OnPropertyChanged(); + OnPropertyChanged(nameof(SizeString)); + OnPropertyChanged(nameof(SizeAvailable)); + } + } + + public bool SizeAvailable { get; set; } + + public string SizeString => ValueFormatUtils.FormatFileSize(Size); + + public SaveModel(SaveDataInfo info) + { + SaveId = info.SaveDataId; + TitleId = info.ProgramId; + UserId = info.UserId; + + var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString); + + InGameList = appData != null; + + if (InGameList) + { + Icon = appData.Icon; + Title = appData.TitleName; + } + else + { + var appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); + Title = appMetadata.Title ?? TitleIdString; + } + + Task.Run(() => + { + var saveRoot = Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}"); + + long totalSize = GetDirectorySize(saveRoot); + + static long GetDirectorySize(string path) + { + long size = 0; + if (Directory.Exists(path)) + { + var directories = Directory.GetDirectories(path); + foreach (var directory in directories) + { + size += GetDirectorySize(directory); + } + + var files = Directory.GetFiles(path); + foreach (var file in files) + { + size += new FileInfo(file).Length; + } + } + + return size; + } + + Size = totalSize; + }); + + } + } +} diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs new file mode 100644 index 00000000..7f04c0ee --- /dev/null +++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class StatusUpdatedEventArgs : EventArgs + { + public bool VSyncEnabled { get; } + public string VolumeStatus { get; } + public string GpuBackend { get; } + public string AspectRatio { get; } + public string DockedMode { get; } + public string FifoStatus { get; } + public string GameStatus { get; } + public string GpuName { get; } + + public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + { + VSyncEnabled = vSyncEnabled; + VolumeStatus = volumeStatus; + GpuBackend = gpuBackend; + DockedMode = dockedMode; + AspectRatio = aspectRatio; + GameStatus = gameStatus; + FifoStatus = fifoStatus; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx/UI/Models/TempProfile.cs b/src/Ryujinx/UI/Models/TempProfile.cs new file mode 100644 index 00000000..659092e6 --- /dev/null +++ b/src/Ryujinx/UI/Models/TempProfile.cs @@ -0,0 +1,61 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; + +namespace Ryujinx.Ava.UI.Models +{ + public class TempProfile : BaseModel + { + private readonly UserProfile _profile; + private byte[] _image; + private string _name = String.Empty; + private UserId _userId; + + public static uint MaxProfileNameLength => 0x20; + + public byte[] Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public UserId UserId + { + get => _userId; + set + { + _userId = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(UserIdString)); + } + } + + public string UserIdString => _userId.ToString(); + + public string Name + { + get => _name; + set + { + _name = value; + OnPropertyChanged(); + } + } + + public TempProfile(UserProfile profile) + { + _profile = profile; + + if (_profile != null) + { + Image = profile.Image; + Name = profile.Name; + UserId = profile.UserId; + } + } + } +} diff --git a/src/Ryujinx/UI/Models/TimeZone.cs b/src/Ryujinx/UI/Models/TimeZone.cs new file mode 100644 index 00000000..950fbce4 --- /dev/null +++ b/src/Ryujinx/UI/Models/TimeZone.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Ava.UI.Models +{ + internal class TimeZone + { + public TimeZone(string utcDifference, string location, string abbreviation) + { + UtcDifference = utcDifference; + Location = location; + Abbreviation = abbreviation; + } + + public string UtcDifference { get; set; } + public string Location { get; set; } + public string Abbreviation { get; set; } + } +} diff --git a/src/Ryujinx/UI/Models/TitleUpdateModel.cs b/src/Ryujinx/UI/Models/TitleUpdateModel.cs new file mode 100644 index 00000000..c270c9ed --- /dev/null +++ b/src/Ryujinx/UI/Models/TitleUpdateModel.cs @@ -0,0 +1,19 @@ +using LibHac.Ns; +using Ryujinx.Ava.Common.Locale; + +namespace Ryujinx.Ava.UI.Models +{ + public class TitleUpdateModel + { + public ApplicationControlProperty Control { get; } + public string Path { get; } + + public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString()); + + public TitleUpdateModel(ApplicationControlProperty control, string path) + { + Control = control; + Path = path; + } + } +} diff --git a/src/Ryujinx/UI/Models/UserProfile.cs b/src/Ryujinx/UI/Models/UserProfile.cs new file mode 100644 index 00000000..7a9237fe --- /dev/null +++ b/src/Ryujinx/UI/Models/UserProfile.cs @@ -0,0 +1,104 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Views.User; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; + +namespace Ryujinx.Ava.UI.Models +{ + public class UserProfile : BaseModel + { + private readonly Profile _profile; + private readonly NavigationDialogHost _owner; + private byte[] _image; + private string _name; + private UserId _userId; + private bool _isPointerOver; + private IBrush _backgroundColor; + + public byte[] Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public UserId UserId + { + get => _userId; + set + { + _userId = value; + OnPropertyChanged(); + } + } + + public string Name + { + get => _name; + set + { + _name = value; + OnPropertyChanged(); + } + } + + public bool IsPointerOver + { + get => _isPointerOver; + set + { + _isPointerOver = value; + OnPropertyChanged(); + } + } + + public IBrush BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + OnPropertyChanged(); + } + } + + public UserProfile(Profile profile, NavigationDialogHost owner) + { + _profile = profile; + _owner = owner; + + UpdateBackground(); + + Image = profile.Image; + Name = profile.Name; + UserId = profile.UserId; + } + + public void UpdateState() + { + UpdateBackground(); + OnPropertyChanged(nameof(Name)); + } + + private void UpdateBackground() + { + var currentApplication = Avalonia.Application.Current; + currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color); + + if (color is not null) + { + BackgroundColor = _profile.AccountState == AccountState.Open ? new SolidColorBrush((Color)color) : Brushes.Transparent; + } + } + + public void Recover(UserProfile userProfile) + { + _owner.Navigate(typeof(UserEditorView), (_owner, userProfile, true)); + } + } +} diff --git a/src/Ryujinx/UI/OpenGLRenderer.cs b/src/Ryujinx/UI/OpenGLRenderer.cs deleted file mode 100644 index 1fdabc75..00000000 --- a/src/Ryujinx/UI/OpenGLRenderer.cs +++ /dev/null @@ -1,142 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Input.HLE; -using SPB.Graphics; -using SPB.Graphics.Exceptions; -using SPB.Graphics.OpenGL; -using SPB.Platform; -using SPB.Platform.GLX; -using SPB.Platform.WGL; -using SPB.Windowing; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.UI -{ - public partial class OpenGLRenderer : RendererWidgetBase - { - private readonly GraphicsDebugLevel _glLogLevel; - - private bool _initializedOpenGL; - - private OpenGLContextBase _openGLContext; - private SwappableNativeWindowBase _nativeWindow; - - public OpenGLRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) - { - _glLogLevel = glLogLevel; - } - - protected override bool OnDrawn(Cairo.Context cr) - { - if (!_initializedOpenGL) - { - IntializeOpenGL(); - } - - return true; - } - - private void IntializeOpenGL() - { - _nativeWindow = RetrieveNativeWindow(); - - Window.EnsureNative(); - - _openGLContext = PlatformHelper.CreateOpenGLContext(GetGraphicsMode(), 3, 3, _glLogLevel == GraphicsDebugLevel.None ? OpenGLContextFlags.Compat : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug); - _openGLContext.Initialize(_nativeWindow); - _openGLContext.MakeCurrent(_nativeWindow); - - // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread. - _openGLContext.MakeCurrent(null); - - WaitEvent.Set(); - - _initializedOpenGL = true; - } - - private SwappableNativeWindowBase RetrieveNativeWindow() - { - if (OperatingSystem.IsWindows()) - { - IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); - - return new WGLWindow(new NativeHandle(windowHandle)); - } - else if (OperatingSystem.IsLinux()) - { - IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); - IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); - - return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); - } - - throw new NotImplementedException(); - } - - [LibraryImport("libgdk-3-0.dll")] - private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); - - [LibraryImport("libgdk-3.so.0")] - private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); - - [LibraryImport("libgdk-3.so.0")] - private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); - - private static FramebufferFormat GetGraphicsMode() - { - return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; - } - - public override void InitializeRenderer() - { - // First take exclusivity on the OpenGL context. - ((Graphics.OpenGL.OpenGLRenderer)Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(_openGLContext)); - - _openGLContext.MakeCurrent(_nativeWindow); - - GL.ClearColor(0, 0, 0, 1.0f); - GL.Clear(ClearBufferMask.ColorBufferBit); - SwapBuffers(); - } - - public override void SwapBuffers() - { - _nativeWindow.SwapBuffers(); - } - - protected override string GetGpuBackendName() - { - return "OpenGL"; - } - - protected override void Dispose(bool disposing) - { - // Try to bind the OpenGL context before calling the shutdown event. - try - { - _openGLContext?.MakeCurrent(_nativeWindow); - } - catch (ContextException e) - { - Logger.Warning?.Print(LogClass.UI, $"Failed to bind OpenGL context: {e}"); - } - - Device?.DisposeGpu(); - NpadManager.Dispose(); - - // Unbind context and destroy everything. - try - { - _openGLContext?.MakeCurrent(null); - } - catch (ContextException e) - { - Logger.Warning?.Print(LogClass.UI, $"Failed to unbind OpenGL context: {e}"); - } - - _openGLContext?.Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/OpenToolkitBindingsContext.cs b/src/Ryujinx/UI/OpenToolkitBindingsContext.cs deleted file mode 100644 index 1224ccfe..00000000 --- a/src/Ryujinx/UI/OpenToolkitBindingsContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -using SPB.Graphics; -using System; - -namespace Ryujinx.UI -{ - public class OpenToolkitBindingsContext : OpenTK.IBindingsContext - { - private readonly IBindingsContext _bindingContext; - - public OpenToolkitBindingsContext(IBindingsContext bindingsContext) - { - _bindingContext = bindingsContext; - } - - public IntPtr GetProcAddress(string procName) - { - return _bindingContext.GetProcAddress(procName); - } - } -} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs new file mode 100644 index 00000000..3bf19b43 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -0,0 +1,294 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using Ryujinx.Common.Configuration; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using SPB.Graphics; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.X11; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindow : NativeControlHost + { + private WindowProc _wndProcDelegate; + private string _className; + + protected GLXWindow X11Window { get; set; } + + protected IntPtr WindowHandle { get; set; } + protected IntPtr X11Display { get; set; } + protected IntPtr NsView { get; set; } + protected IntPtr MetalLayer { get; set; } + + public delegate void UpdateBoundsCallbackDelegate(Rect rect); + private UpdateBoundsCallbackDelegate _updateBoundsCallback; + + public event EventHandler<IntPtr> WindowCreated; + public event EventHandler<Size> BoundsChanged; + + public EmbeddedWindow() + { + this.GetObservable(BoundsProperty).Subscribe(StateChanged); + + Initialized += OnNativeEmbeddedWindowCreated; + } + + public virtual void OnWindowCreated() { } + + protected virtual void OnWindowDestroyed() { } + + protected virtual void OnWindowDestroying() + { + WindowHandle = IntPtr.Zero; + X11Display = IntPtr.Zero; + NsView = IntPtr.Zero; + MetalLayer = IntPtr.Zero; + } + + private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e) + { + OnWindowCreated(); + + Task.Run(() => + { + WindowCreated?.Invoke(this, WindowHandle); + }); + } + + private void StateChanged(Rect rect) + { + BoundsChanged?.Invoke(this, rect.Size); + _updateBoundsCallback?.Invoke(rect); + } + + protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control) + { + if (OperatingSystem.IsLinux()) + { + return CreateLinux(control); + } + + if (OperatingSystem.IsWindows()) + { + return CreateWin32(control); + } + + if (OperatingSystem.IsMacOS()) + { + return CreateMacOS(); + } + + return base.CreateNativeControlCore(control); + } + + protected override void DestroyNativeControlCore(IPlatformHandle control) + { + OnWindowDestroying(); + + if (OperatingSystem.IsLinux()) + { + DestroyLinux(); + } + else if (OperatingSystem.IsWindows()) + { + DestroyWin32(control); + } + else if (OperatingSystem.IsMacOS()) + { + DestroyMacOS(); + } + else + { + base.DestroyNativeControlCore(control); + } + + OnWindowDestroyed(); + } + + [SupportedOSPlatform("linux")] + private IPlatformHandle CreateLinux(IPlatformHandle control) + { + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle)); + X11Window.Hide(); + } + else + { + X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow; + } + + WindowHandle = X11Window.WindowHandle.RawHandle; + X11Display = X11Window.DisplayHandle.RawHandle; + + return new PlatformHandle(WindowHandle, "X11"); + } + + [SupportedOSPlatform("windows")] + IPlatformHandle CreateWin32(IPlatformHandle control) + { + _className = "NativeWindow-" + Guid.NewGuid(); + + _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) + { + if (VisualRoot != null) + { + if (msg == WindowsMessages.Lbuttondown || + msg == WindowsMessages.Rbuttondown || + msg == WindowsMessages.Lbuttonup || + msg == WindowsMessages.Rbuttonup || + msg == WindowsMessages.Mousemove) + { + Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value; + Pointer pointer = new(0, PointerType.Mouse, true); + +#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete) + switch (msg) + { + case WindowsMessages.Lbuttondown: + case WindowsMessages.Rbuttondown: + { + bool isLeft = msg == WindowsMessages.Lbuttondown; + RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; + PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed); + + var evnt = new PointerPressedEventArgs( + this, + pointer, + this, + rootVisualPosition, + (ulong)Environment.TickCount64, + properties, + KeyModifiers.None); + + RaiseEvent(evnt); + + break; + } + case WindowsMessages.Lbuttonup: + case WindowsMessages.Rbuttonup: + { + bool isLeft = msg == WindowsMessages.Lbuttonup; + RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; + PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased); + + var evnt = new PointerReleasedEventArgs( + this, + pointer, + this, + rootVisualPosition, + (ulong)Environment.TickCount64, + properties, + KeyModifiers.None, + isLeft ? MouseButton.Left : MouseButton.Right); + + RaiseEvent(evnt); + + break; + } + case WindowsMessages.Mousemove: + { + var evnt = new PointerEventArgs( + PointerMovedEvent, + this, + pointer, + this, + rootVisualPosition, + (ulong)Environment.TickCount64, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other), + KeyModifiers.None); + + RaiseEvent(evnt); + + break; + } + } +#pragma warning restore CS0618 + } + } + + return DefWindowProc(hWnd, msg, wParam, lParam); + }; + + WndClassEx wndClassEx = new() + { + cbSize = Marshal.SizeOf<WndClassEx>(), + hInstance = GetModuleHandle(null), + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), + style = ClassStyles.CsOwndc, + lpszClassName = Marshal.StringToHGlobalUni(_className), + hCursor = CreateArrowCursor(), + }; + + RegisterClassEx(ref wndClassEx); + + WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + Marshal.FreeHGlobal(wndClassEx.lpszClassName); + + return new PlatformHandle(WindowHandle, "HWND"); + } + + [SupportedOSPlatform("macos")] + IPlatformHandle CreateMacOS() + { + // Create a new CAMetalLayer. + ObjectiveC.Object layerObject = new("CAMetalLayer"); + ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc"); + metalLayer.SendMessage("init"); + + // Create a child NSView to render into. + ObjectiveC.Object nsViewObject = new("NSView"); + ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc"); + child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0)); + + // Make its renderer our metal layer. + child.SendMessage("setWantsLayer:", 1); + child.SendMessage("setLayer:", metalLayer); + metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); + + // Ensure the scale factor is up to date. + _updateBoundsCallback = rect => + { + metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); + }; + + IntPtr nsView = child.ObjPtr; + MetalLayer = metalLayer.ObjPtr; + NsView = nsView; + + return new PlatformHandle(nsView, "NSView"); + } + + [SupportedOSPlatform("Linux")] + void DestroyLinux() + { + X11Window?.Dispose(); + } + + [SupportedOSPlatform("windows")] + void DestroyWin32(IPlatformHandle handle) + { + DestroyWindow(handle.Handle); + UnregisterClass(_className, GetModuleHandle(null)); + } + + [SupportedOSPlatform("macos")] +#pragma warning disable CA1822 // Mark member as static + void DestroyMacOS() + { + // TODO + } +#pragma warning restore CA1822 + } +} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs new file mode 100644 index 00000000..3842301d --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs @@ -0,0 +1,94 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.UI.Common.Configuration; +using SPB.Graphics; +using SPB.Graphics.Exceptions; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Platform.WGL; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindowOpenGL : EmbeddedWindow + { + private SwappableNativeWindowBase _window; + + public OpenGLContextBase Context { get; set; } + + protected override void OnWindowDestroying() + { + Context.Dispose(); + + base.OnWindowDestroying(); + } + + public override void OnWindowCreated() + { + base.OnWindowCreated(); + + if (OperatingSystem.IsWindows()) + { + _window = new WGLWindow(new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + _window = X11Window; + } + else + { + throw new PlatformNotSupportedException(); + } + + var flags = OpenGLContextFlags.Compat; + if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None) + { + flags |= OpenGLContextFlags.Debug; + } + + var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; + + Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags); + + Context.Initialize(_window); + Context.MakeCurrent(_window); + + GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress)); + + Context.MakeCurrent(null); + } + + public void MakeCurrent(bool unbind = false, bool shouldThrow = true) + { + try + { + Context?.MakeCurrent(!unbind ? _window : null); + } + catch (ContextException e) + { + if (shouldThrow) + { + throw; + } + + Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}"); + } + } + + public void SwapBuffers() + { + _window?.SwapBuffers(); + } + + public void InitializeBackgroundContext(IRenderer renderer) + { + (renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context)); + + MakeCurrent(); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs new file mode 100644 index 00000000..fafbec20 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs @@ -0,0 +1,42 @@ +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; +using SPB.Platform.Metal; +using SPB.Platform.Win32; +using SPB.Platform.X11; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindowVulkan : EmbeddedWindow + { + public SurfaceKHR CreateSurface(Instance instance) + { + NativeWindowBase nativeWindowBase; + + if (OperatingSystem.IsWindows()) + { + nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsMacOS()) + { + nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer)); + } + else + { + throw new PlatformNotSupportedException(); + } + + return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase)); + } + + public SurfaceKHR CreateSurface(Instance instance, Vk _) + { + return CreateSurface(instance); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs b/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs new file mode 100644 index 00000000..85e8585f --- /dev/null +++ b/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs @@ -0,0 +1,20 @@ +using OpenTK; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + internal class OpenTKBindingsContext : IBindingsContext + { + private readonly Func<string, IntPtr> _getProcAddress; + + public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress) + { + _getProcAddress = getProcAddress; + } + + public IntPtr GetProcAddress(string procName) + { + return _getProcAddress(procName); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml b/src/Ryujinx/UI/Renderer/RendererHost.axaml new file mode 100644 index 00000000..e0b586b4 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/RendererHost.axaml @@ -0,0 +1,12 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + d:DesignWidth="800" + d:DesignHeight="450" + x:Class="Ryujinx.Ava.UI.Renderer.RendererHost" + FlowDirection="LeftToRight" + Focusable="True"> +</UserControl> diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs new file mode 100644 index 00000000..d055d9ea --- /dev/null +++ b/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs @@ -0,0 +1,68 @@ +using Avalonia; +using Avalonia.Controls; +using Ryujinx.Common.Configuration; +using Ryujinx.UI.Common.Configuration; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public partial class RendererHost : UserControl, IDisposable + { + public readonly EmbeddedWindow EmbeddedWindow; + + public event EventHandler<EventArgs> WindowCreated; + public event Action<object, Size> BoundsChanged; + + public RendererHost() + { + InitializeComponent(); + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) + { + EmbeddedWindow = new EmbeddedWindowOpenGL(); + } + else + { + EmbeddedWindow = new EmbeddedWindowVulkan(); + } + + Initialize(); + } + + private void Initialize() + { + EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated; + EmbeddedWindow.BoundsChanged += CurrentWindow_BoundsChanged; + + Content = EmbeddedWindow; + } + + public void Dispose() + { + if (EmbeddedWindow != null) + { + EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated; + EmbeddedWindow.BoundsChanged -= CurrentWindow_BoundsChanged; + } + + GC.SuppressFinalize(this); + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + Dispose(); + } + + private void CurrentWindow_BoundsChanged(object sender, Size e) + { + BoundsChanged?.Invoke(sender, e); + } + + private void CurrentWindow_WindowCreated(object sender, IntPtr e) + { + WindowCreated?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs b/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs new file mode 100644 index 00000000..63bf6cf7 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.OpenGL; +using SPB.Graphics; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Windowing; + +namespace Ryujinx.Ava.UI.Renderer +{ + class SPBOpenGLContext : IOpenGLContext + { + private readonly OpenGLContextBase _context; + private readonly NativeWindowBase _window; + + private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) + { + _context = context; + _window = window; + } + + public void Dispose() + { + _context.Dispose(); + _window.Dispose(); + } + + public void MakeCurrent() + { + _context.MakeCurrent(_window); + } + + public bool HasContext() => _context.IsCurrent; + + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) + { + OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); + NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); + + context.Initialize(window); + context.MakeCurrent(window); + + GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress)); + + context.MakeCurrent(null); + + return new SPBOpenGLContext(context, window); + } + } +} diff --git a/src/Ryujinx/UI/RendererWidgetBase.cs b/src/Ryujinx/UI/RendererWidgetBase.cs deleted file mode 100644 index e27d0604..00000000 --- a/src/Ryujinx/UI/RendererWidgetBase.cs +++ /dev/null @@ -1,803 +0,0 @@ -using Gdk; -using Gtk; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.GAL.Multithreading; -using Ryujinx.Graphics.Gpu; -using Ryujinx.Input; -using Ryujinx.Input.GTK3; -using Ryujinx.Input.HLE; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; -using Key = Ryujinx.Input.Key; -using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; -using Switch = Ryujinx.HLE.Switch; - -namespace Ryujinx.UI -{ - public abstract class RendererWidgetBase : DrawingArea - { - private const int SwitchPanelWidth = 1280; - private const int SwitchPanelHeight = 720; - private const int TargetFps = 60; - private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. - private const float VolumeDelta = 0.05f; - - public ManualResetEvent WaitEvent { get; set; } - public NpadManager NpadManager { get; } - public TouchScreenManager TouchScreenManager { get; } - public Switch Device { get; private set; } - public IRenderer Renderer { get; private set; } - - public bool ScreenshotRequested { get; set; } - protected int WindowWidth { get; private set; } - protected int WindowHeight { get; private set; } - - public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; - - private bool _isActive; - private bool _isStopped; - - private bool _toggleFullscreen; - private bool _toggleDockedMode; - - private readonly long _ticksPerFrame; - - private long _ticks = 0; - private float _newVolume; - - private readonly Stopwatch _chrono; - - private KeyboardHotkeyState _prevHotkeyState; - - private readonly ManualResetEvent _exitEvent; - private readonly ManualResetEvent _gpuDoneEvent; - - private readonly CancellationTokenSource _gpuCancellationTokenSource; - - // Hide Cursor - const int CursorHideIdleTime = 5; // seconds - private static readonly Cursor _invisibleCursor = new(Display.Default, CursorType.BlankCursor); - private long _lastCursorMoveTime; - private HideCursorMode _hideCursorMode; - private readonly InputManager _inputManager; - private readonly IKeyboard _keyboardInterface; - private readonly GraphicsDebugLevel _glLogLevel; - private string _gpuBackendName; - private string _gpuDriverName; - private bool _isMouseInClient; - - public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel) - { - var mouseDriver = new GTK3MouseDriver(this); - - _inputManager = inputManager; - _inputManager.SetMouseDriver(mouseDriver); - NpadManager = _inputManager.CreateNpadManager(); - TouchScreenManager = _inputManager.CreateTouchScreenManager(); - _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); - - WaitEvent = new ManualResetEvent(false); - - _glLogLevel = glLogLevel; - - Destroyed += Renderer_Destroyed; - - _chrono = new Stopwatch(); - - _ticksPerFrame = Stopwatch.Frequency / TargetFps; - - AddEvents((int)(EventMask.ButtonPressMask - | EventMask.ButtonReleaseMask - | EventMask.PointerMotionMask - | EventMask.ScrollMask - | EventMask.EnterNotifyMask - | EventMask.LeaveNotifyMask - | EventMask.KeyPressMask - | EventMask.KeyReleaseMask)); - - _exitEvent = new ManualResetEvent(false); - _gpuDoneEvent = new ManualResetEvent(false); - - _gpuCancellationTokenSource = new CancellationTokenSource(); - - _hideCursorMode = ConfigurationState.Instance.HideCursor; - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - - ConfigurationState.Instance.HideCursor.Event += HideCursorStateChanged; - ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing; - ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; - ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; - } - - private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e) - { - Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - } - - private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e) - { - Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - } - - public abstract void InitializeRenderer(); - - public abstract void SwapBuffers(); - - protected abstract string GetGpuBackendName(); - - private string GetGpuDriverName() - { - return Renderer.GetHardwareInfo().GpuDriver; - } - - private void HideCursorStateChanged(object sender, ReactiveEventArgs<HideCursorMode> state) - { - Application.Invoke(delegate - { - _hideCursorMode = state.NewValue; - - switch (_hideCursorMode) - { - case HideCursorMode.Never: - Window.Cursor = null; - break; - case HideCursorMode.OnIdle: - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - break; - case HideCursorMode.Always: - Window.Cursor = _invisibleCursor; - break; - default: - throw new ArgumentOutOfRangeException(nameof(state)); - } - }); - } - - private void Renderer_Destroyed(object sender, EventArgs e) - { - ConfigurationState.Instance.HideCursor.Event -= HideCursorStateChanged; - ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing; - ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; - ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; - - NpadManager.Dispose(); - Dispose(); - } - - private void UpdateAnriAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e) - { - Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); - } - - protected override bool OnMotionNotifyEvent(EventMotion evnt) - { - if (_hideCursorMode == HideCursorMode.OnIdle) - { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - } - - if (ConfigurationState.Instance.Hid.EnableMouse) - { - Window.Cursor = _invisibleCursor; - } - - _isMouseInClient = true; - - return false; - } - - protected override bool OnEnterNotifyEvent(EventCrossing evnt) - { - Window.Cursor = ConfigurationState.Instance.Hid.EnableMouse ? _invisibleCursor : null; - - _isMouseInClient = true; - - return base.OnEnterNotifyEvent(evnt); - } - - protected override bool OnLeaveNotifyEvent(EventCrossing evnt) - { - Window.Cursor = null; - - _isMouseInClient = false; - - return base.OnLeaveNotifyEvent(evnt); - } - - protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight) - { - Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); - - // If the monitor is at least 1080p, use the Switch panel size as minimal size. - if (monitor.Geometry.Height >= 1080) - { - minimumHeight = SwitchPanelHeight; - } - // Otherwise, we default minimal size to 480p 16:9. - else - { - minimumHeight = 480; - } - - naturalHeight = minimumHeight; - } - - protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth) - { - Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); - - // If the monitor is at least 1080p, use the Switch panel size as minimal size. - if (monitor.Geometry.Height >= 1080) - { - minimumWidth = SwitchPanelWidth; - } - // Otherwise, we default minimal size to 480p 16:9. - else - { - minimumWidth = 854; - } - - naturalWidth = minimumWidth; - } - - protected override bool OnConfigureEvent(EventConfigure evnt) - { - bool result = base.OnConfigureEvent(evnt); - - Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); - - WindowWidth = evnt.Width * monitor.ScaleFactor; - WindowHeight = evnt.Height * monitor.ScaleFactor; - - Renderer?.Window?.SetSize(WindowWidth, WindowHeight); - - return result; - } - - private void HandleScreenState(KeyboardStateSnapshot keyboard) - { - bool toggleFullscreen = keyboard.IsPressed(Key.F11) - || ((keyboard.IsPressed(Key.AltLeft) - || keyboard.IsPressed(Key.AltRight)) - && keyboard.IsPressed(Key.Enter)) - || keyboard.IsPressed(Key.Escape); - - bool fullScreenToggled = ParentWindow.State.HasFlag(WindowState.Fullscreen); - - if (toggleFullscreen != _toggleFullscreen) - { - if (toggleFullscreen) - { - if (fullScreenToggled) - { - ParentWindow.Unfullscreen(); - (Toplevel as MainWindow)?.ToggleExtraWidgets(true); - } - else - { - if (keyboard.IsPressed(Key.Escape)) - { - if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) - { - Exit(); - } - } - else - { - ParentWindow.Fullscreen(); - (Toplevel as MainWindow)?.ToggleExtraWidgets(false); - } - } - } - } - - _toggleFullscreen = toggleFullscreen; - - bool toggleDockedMode = keyboard.IsPressed(Key.F9); - - if (toggleDockedMode != _toggleDockedMode) - { - if (toggleDockedMode) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = - !ConfigurationState.Instance.System.EnableDockedMode.Value; - } - } - - _toggleDockedMode = toggleDockedMode; - - if (_isMouseInClient) - { - if (ConfigurationState.Instance.Hid.EnableMouse.Value) - { - Window.Cursor = _invisibleCursor; - } - else - { - switch (_hideCursorMode) - { - case HideCursorMode.OnIdle: - long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; - Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null; - break; - case HideCursorMode.Always: - Window.Cursor = _invisibleCursor; - break; - case HideCursorMode.Never: - Window.Cursor = null; - break; - } - } - } - } - - public void Initialize(Switch device) - { - Device = device; - - IRenderer renderer = Device.Gpu.Renderer; - - if (renderer is ThreadedRenderer tr) - { - renderer = tr.BaseRenderer; - } - - Renderer = renderer; - Renderer?.Window?.SetSize(WindowWidth, WindowHeight); - - if (Renderer != null) - { - Renderer.ScreenCaptured += Renderer_ScreenCaptured; - } - - NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); - TouchScreenManager.Initialize(device); - } - - private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) - { - if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) - { - Task.Run(() => - { - lock (this) - { - var currentTime = DateTime.Now; - string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; - string directory = AppDataManager.Mode switch - { - AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), - _ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), - }; - - string path = System.IO.Path.Combine(directory, filename); - - try - { - Directory.CreateDirectory(directory); - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); - - return; - } - - Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) - : Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); - - if (e.FlipX) - { - image.Mutate(x => x.Flip(FlipMode.Horizontal)); - } - - if (e.FlipY) - { - image.Mutate(x => x.Flip(FlipMode.Vertical)); - } - - image.SaveAsPng(path, new PngEncoder() - { - ColorType = PngColorType.Rgb, - }); - - image.Dispose(); - - Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); - } - }); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); - } - } - - public void Render() - { - Gtk.Window parent = Toplevel as Gtk.Window; - parent.Present(); - - InitializeRenderer(); - - Device.Gpu.Renderer.Initialize(_glLogLevel); - - Renderer.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); - Renderer.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); - Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); - - _gpuBackendName = GetGpuBackendName(); - _gpuDriverName = GetGpuDriverName(); - - Device.Gpu.Renderer.RunLoop(() => - { - Device.Gpu.SetGpuThread(); - Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); - - Renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); - - (Toplevel as MainWindow)?.ActivatePauseMenu(); - - while (_isActive) - { - if (_isStopped) - { - return; - } - - _ticks += _chrono.ElapsedTicks; - - _chrono.Restart(); - - if (Device.WaitFifo()) - { - Device.Statistics.RecordFifoStart(); - Device.ProcessFrame(); - Device.Statistics.RecordFifoEnd(); - } - - while (Device.ConsumeFrameAvailable()) - { - Device.PresentFrame(SwapBuffers); - } - - if (_ticks >= _ticksPerFrame) - { - string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld"; - float scale = GraphicsConfig.ResScale; - if (scale != 1) - { - dockedMode += $" ({scale}x)"; - } - - StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, - Device.GetVolume(), - _gpuBackendName, - dockedMode, - ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), - $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", - $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", - $"GPU: {_gpuDriverName}")); - - _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); - } - } - - // Make sure all commands in the run loop are fully executed before leaving the loop. - if (Device.Gpu.Renderer is ThreadedRenderer threaded) - { - threaded.FlushThreadedCommands(); - } - - _gpuDoneEvent.Set(); - }); - } - - public void Start() - { - _chrono.Restart(); - - _isActive = true; - - Gtk.Window parent = Toplevel as Gtk.Window; - - Application.Invoke(delegate - { - parent.Present(); - - var activeProcess = Device.Processes.ActiveApplication; - - parent.Title = TitleHelper.ActiveApplicationTitle(activeProcess, Program.Version); - }); - - Thread renderLoopThread = new(Render) - { - Name = "GUI.RenderLoop", - }; - renderLoopThread.Start(); - - Thread nvidiaStutterWorkaround = null; - if (Renderer is Graphics.OpenGL.OpenGLRenderer) - { - nvidiaStutterWorkaround = new Thread(NvidiaStutterWorkaround) - { - Name = "GUI.NvidiaStutterWorkaround", - }; - nvidiaStutterWorkaround.Start(); - } - - MainLoop(); - - // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. - // We only need to wait for all commands submitted during the main gpu loop to be processed. - _gpuDoneEvent.WaitOne(); - _gpuDoneEvent.Dispose(); - nvidiaStutterWorkaround?.Join(); - - Exit(); - } - - public void Exit() - { - TouchScreenManager?.Dispose(); - NpadManager?.Dispose(); - - if (_isStopped) - { - return; - } - - _gpuCancellationTokenSource.Cancel(); - - _isStopped = true; - - if (_isActive) - { - _isActive = false; - - _exitEvent.WaitOne(); - _exitEvent.Dispose(); - } - } - - private void NvidiaStutterWorkaround() - { - while (_isActive) - { - // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. - // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. - // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ. - // This creates a new thread every second or so. - // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics. - // This is a little over budget on a frame time of 16ms, so creates a large stutter. - // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread. - - // TODO: This should be removed when the issue with the GateThread is resolved. - - ThreadPool.QueueUserWorkItem((state) => { }); - Thread.Sleep(300); - } - } - - public void MainLoop() - { - while (_isActive) - { - UpdateFrame(); - - // Polling becomes expensive if it's not slept - Thread.Sleep(1); - } - - _exitEvent.Set(); - } - - private bool UpdateFrame() - { - if (!_isActive) - { - return true; - } - - if (_isStopped) - { - return false; - } - - if ((Toplevel as MainWindow).IsFocused) - { - Application.Invoke(delegate - { - KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); - - HandleScreenState(keyboard); - - if (keyboard.IsPressed(Key.Delete)) - { - if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) - { - Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); - } - } - }); - } - - NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - - if ((Toplevel as MainWindow).IsFocused) - { - KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync)) - { - Device.EnableDeviceVsync = !Device.EnableDeviceVsync; - } - - if ((currentHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot)) || ScreenshotRequested) - { - ScreenshotRequested = false; - - Renderer.Screenshot(); - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI)) - { - (Toplevel as MainWindow).ToggleExtraWidgets(true); - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.Pause) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Pause)) - { - (Toplevel as MainWindow)?.TogglePause(); - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute)) - { - if (Device.IsAudioMuted()) - { - Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); - } - else - { - Device.SetVolume(0); - } - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp)) - { - GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown)) - { - GraphicsConfig.ResScale = - (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp)) - { - _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); - Device.SetVolume(_newVolume); - } - - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown)) - { - _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); - Device.SetVolume(_newVolume); - } - - _prevHotkeyState = currentHotkeyState; - } - - // Touchscreen - bool hasTouch = false; - - // Get screen touch position - if ((Toplevel as MainWindow).IsFocused && !ConfigurationState.Instance.Hid.EnableMouse) - { - hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as GTK3MouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - } - - if (!hasTouch) - { - TouchScreenManager.Update(false); - } - - Device.Hid.DebugPad.Update(); - - return true; - } - - [Flags] - private enum KeyboardHotkeyState - { - None = 0, - ToggleVSync = 1 << 0, - Screenshot = 1 << 1, - ShowUI = 1 << 2, - Pause = 1 << 3, - ToggleMute = 1 << 4, - ResScaleUp = 1 << 5, - ResScaleDown = 1 << 6, - VolumeUp = 1 << 7, - VolumeDown = 1 << 8, - } - - private KeyboardHotkeyState GetHotkeyState() - { - KeyboardHotkeyState state = KeyboardHotkeyState.None; - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) - { - state |= KeyboardHotkeyState.ToggleVSync; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) - { - state |= KeyboardHotkeyState.Screenshot; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) - { - state |= KeyboardHotkeyState.ShowUI; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) - { - state |= KeyboardHotkeyState.Pause; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) - { - state |= KeyboardHotkeyState.ToggleMute; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) - { - state |= KeyboardHotkeyState.ResScaleUp; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) - { - state |= KeyboardHotkeyState.ResScaleDown; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) - { - state |= KeyboardHotkeyState.VolumeUp; - } - - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) - { - state |= KeyboardHotkeyState.VolumeDown; - } - - return state; - } - } -} diff --git a/src/Ryujinx/UI/SPBOpenGLContext.cs b/src/Ryujinx/UI/SPBOpenGLContext.cs deleted file mode 100644 index 97feb434..00000000 --- a/src/Ryujinx/UI/SPBOpenGLContext.cs +++ /dev/null @@ -1,49 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.OpenGL; -using SPB.Graphics; -using SPB.Graphics.OpenGL; -using SPB.Platform; -using SPB.Windowing; - -namespace Ryujinx.UI -{ - class SPBOpenGLContext : IOpenGLContext - { - private readonly OpenGLContextBase _context; - private readonly NativeWindowBase _window; - - private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) - { - _context = context; - _window = window; - } - - public void Dispose() - { - _context.Dispose(); - _window.Dispose(); - } - - public void MakeCurrent() - { - _context.MakeCurrent(_window); - } - - public bool HasContext() => _context.IsCurrent; - - public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) - { - OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); - NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); - - context.Initialize(window); - context.MakeCurrent(window); - - GL.LoadBindings(new OpenToolkitBindingsContext(context)); - - context.MakeCurrent(null); - - return new SPBOpenGLContext(context, window); - } - } -} diff --git a/src/Ryujinx/UI/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/StatusUpdatedEventArgs.cs deleted file mode 100644 index db467ebf..00000000 --- a/src/Ryujinx/UI/StatusUpdatedEventArgs.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Ryujinx.UI -{ - public class StatusUpdatedEventArgs : EventArgs - { - public bool VSyncEnabled; - public float Volume; - public string DockedMode; - public string AspectRatio; - public string GameStatus; - public string FifoStatus; - public string GpuName; - public string GpuBackend; - - public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) - { - VSyncEnabled = vSyncEnabled; - Volume = volume; - GpuBackend = gpuBackend; - DockedMode = dockedMode; - AspectRatio = aspectRatio; - GameStatus = gameStatus; - FifoStatus = fifoStatus; - GpuName = gpuName; - } - } -} diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs new file mode 100644 index 00000000..6020f40e --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -0,0 +1,131 @@ +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class AboutWindowViewModel : BaseModel + { + private Bitmap _githubLogo; + private Bitmap _discordLogo; + private Bitmap _patreonLogo; + private Bitmap _twitterLogo; + + private string _version; + private string _supporters; + + public Bitmap GithubLogo + { + get => _githubLogo; + set + { + _githubLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap DiscordLogo + { + get => _discordLogo; + set + { + _discordLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap PatreonLogo + { + get => _patreonLogo; + set + { + _patreonLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap TwitterLogo + { + get => _twitterLogo; + set + { + _twitterLogo = value; + OnPropertyChanged(); + } + } + + public string Supporters + { + get => _supporters; + set + { + _supporters = value; + OnPropertyChanged(); + } + } + + public string Version + { + get => _version; + set + { + _version = value; + OnPropertyChanged(); + } + } + + public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); + + public AboutWindowViewModel() + { + Version = Program.Version; + + 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"))); + } + 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"))); + } + + Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson); + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError]; + + return; + } + + HttpClient httpClient = new(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n"; + } + catch + { + Supporters = LocaleManager.Instance[LocaleKeys.ApiError]; + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs new file mode 100644 index 00000000..8f09568a --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs @@ -0,0 +1,518 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Models.Amiibo; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class AmiiboWindowViewModel : BaseModel, IDisposable + { + private const string DefaultJson = "{ \"amiibo\": [] }"; + private const float AmiiboImageSize = 350f; + + private readonly string _amiiboJsonPath; + private readonly byte[] _amiiboLogoBytes; + private readonly HttpClient _httpClient; + private readonly StyleableWindow _owner; + + private Bitmap _amiiboImage; + private List<AmiiboApi> _amiiboList; + private AvaloniaList<AmiiboApi> _amiibos; + private ObservableCollection<string> _amiiboSeries; + + private int _amiiboSelectedIndex; + private int _seriesSelectedIndex; + private bool _enableScanning; + private bool _showAllAmiibo; + private bool _useRandomUuid; + private string _usage; + + private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId) + { + _owner = owner; + + _httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30), + }; + + LastScannedAmiiboId = lastScannedAmiiboId; + TitleId = titleId; + + Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); + _amiiboList = new List<AmiiboApi>(); + _amiiboSeries = new ObservableCollection<string>(); + _amiibos = new AvaloniaList<AmiiboApi>(); + + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); + + _ = LoadContentAsync(); + } + + public AmiiboWindowViewModel() { } + + public string TitleId { get; set; } + public string LastScannedAmiiboId { get; set; } + + public UserResult Response { get; private set; } + + public bool UseRandomUuid + { + get => _useRandomUuid; + set + { + _useRandomUuid = value; + + OnPropertyChanged(); + } + } + + public bool ShowAllAmiibo + { + get => _showAllAmiibo; + set + { + _showAllAmiibo = value; + + ParseAmiiboData(); + + OnPropertyChanged(); + } + } + + public AvaloniaList<AmiiboApi> AmiiboList + { + get => _amiibos; + set + { + _amiibos = value; + + OnPropertyChanged(); + } + } + + public ObservableCollection<string> AmiiboSeries + { + get => _amiiboSeries; + set + { + _amiiboSeries = value; + OnPropertyChanged(); + } + } + + public int SeriesSelectedIndex + { + get => _seriesSelectedIndex; + set + { + _seriesSelectedIndex = value; + + FilterAmiibo(); + + OnPropertyChanged(); + } + } + + public int AmiiboSelectedIndex + { + get => _amiiboSelectedIndex; + set + { + _amiiboSelectedIndex = value; + + EnableScanning = _amiiboSelectedIndex >= 0 && _amiiboSelectedIndex < _amiibos.Count; + + SetAmiiboDetails(); + + OnPropertyChanged(); + } + } + + public Bitmap AmiiboImage + { + get => _amiiboImage; + set + { + _amiiboImage = value; + + OnPropertyChanged(); + } + } + + public string Usage + { + get => _usage; + set + { + _usage = value; + + OnPropertyChanged(); + } + } + + public bool EnableScanning + { + get => _enableScanning; + set + { + _enableScanning = value; + + OnPropertyChanged(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _httpClient.Dispose(); + } + + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + + try + { + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + + return true; + } + catch (JsonException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + } + + private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson() + { + bool localIsValid = false; + bool remoteIsValid = false; + AmiiboJson amiiboJson = new(); + + try + { + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } + + if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) + { + remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); + } + } + catch (Exception exception) + { + if (!(localIsValid || remoteIsValid)) + { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + + // Neither local or remote files are valid JSON, close window. + ShowInfoDialog(); + Close(); + } + else if (!remoteIsValid) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + + // Only the local file is valid, the local one should be used + // but the user should be warned. + ShowInfoDialog(); + } + } + + return amiiboJson; + } + + private async Task LoadContentAsync() + { + AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); + + _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); + + ParseAmiiboData(); + } + + private void ParseAmiiboData() + { + _amiiboSeries.Clear(); + _amiibos.Clear(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries)) + { + if (!ShowAllAmiibo) + { + foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); + + break; + } + } + } + } + else + { + AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); + } + } + } + + if (LastScannedAmiiboId != "") + { + SelectLastScannedAmiibo(); + } + else + { + SeriesSelectedIndex = 0; + } + } + + private void SelectLastScannedAmiibo() + { + AmiiboApi scanned = _amiiboList.Find(amiibo => amiibo.GetId() == LastScannedAmiiboId); + + SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries); + AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); + } + + private void FilterAmiibo() + { + _amiibos.Clear(); + + if (_seriesSelectedIndex < 0) + { + return; + } + + List<AmiiboApi> amiiboSortedList = _amiiboList + .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) + .OrderBy(amiibo => amiibo.Name).ToList(); + + for (int i = 0; i < amiiboSortedList.Count; i++) + { + if (!_amiibos.Contains(amiiboSortedList[i])) + { + if (!_showAllAmiibo) + { + foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + _amiibos.Add(amiiboSortedList[i]); + + break; + } + } + } + } + else + { + _amiibos.Add(amiiboSortedList[i]); + } + } + } + + AmiiboSelectedIndex = 0; + } + + private void SetAmiiboDetails() + { + ResetAmiiboPreview(); + + Usage = string.Empty; + + if (_amiiboSelectedIndex < 0) + { + return; + } + + AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; + + string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image; + + StringBuilder usageStringBuilder = new(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (_amiiboList[i].Equals(selected)) + { + bool writable = false; + + foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) + { + if (item.GameId.Contains(TitleId)) + { + foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) + { + usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); + + writable = usageItem.Write; + } + } + } + + if (usageStringBuilder.Length == 0) + { + usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}."); + } + + Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}"; + } + } + + _ = UpdateAmiiboPreview(imageUrl); + } + + private async Task<bool> NeedsUpdate(DateTime oldLastModified) + { + try + { + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); + } + + return false; + } + + private async Task<string> DownloadAmiiboJson() + { + try + { + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); + + if (response.IsSuccessStatusCode) + { + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; + } + + Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); + } + + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], + LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + return null; + } + + private void Close() + { + Dispatcher.UIThread.Post(_owner.Close); + } + + private async Task UpdateAmiiboPreview(string imageUrl) + { + HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); + + if (response.IsSuccessStatusCode) + { + byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); + using MemoryStream memoryStream = new(amiiboPreviewBytes); + + Bitmap bitmap = new(memoryStream); + + double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width, + AmiiboImageSize / bitmap.Size.Height); + + int resizeHeight = (int)(bitmap.Size.Height * ratio); + int resizeWidth = (int)(bitmap.Size.Width * ratio); + + AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight)); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}"); + } + } + + private void ResetAmiiboPreview() + { + using MemoryStream memoryStream = new(_amiiboLogoBytes); + + Bitmap bitmap = new(memoryStream); + + AmiiboImage = bitmap; + } + + private static async void ShowInfoDialog() + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], + LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/BaseModel.cs b/src/Ryujinx/UI/ViewModels/BaseModel.cs new file mode 100644 index 00000000..4db9cf81 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/BaseModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class BaseModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs new file mode 100644 index 00000000..71ad2c12 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs @@ -0,0 +1,897 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Views.Input; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Input; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +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 KeyboardString = "keyboard"; + private const string ControllerString = "controller"; + private readonly MainWindow _mainWindow; + + private PlayerIndex _playerId; + private int _controller; + private int _controllerNumber; + private string _controllerImage; + private int _device; + private object _configuration; + private string _profileName; + private bool _isLoaded; + + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public IGamepadDriver AvaloniaKeyboardDriver { get; } + public IGamepad SelectedGamepad { get; private set; } + + public ObservableCollection<PlayerModel> PlayerIndexes { get; set; } + public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } + internal ObservableCollection<ControllerModel> Controllers { get; set; } + public AvaloniaList<string> ProfilesList { get; set; } + public AvaloniaList<string> DeviceList { get; set; } + + // XAML Flags + public bool ShowSettings => _device > 0; + public bool IsController => _device > 1; + public bool IsKeyboard => !IsController; + public bool IsRight { get; set; } + public bool IsLeft { get; set; } + + public bool IsModified { get; set; } + + public object Configuration + { + get => _configuration; + set + { + _configuration = value; + + OnPropertyChanged(); + } + } + + public PlayerIndex PlayerId + { + get => _playerId; + set + { + if (IsModified) + { + return; + } + + IsModified = false; + _playerId = value; + + if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) + { + _playerId = PlayerIndex.Player1; + } + + LoadConfiguration(); + LoadDevice(); + LoadProfiles(); + + _isLoaded = true; + + OnPropertyChanged(); + } + } + + public int Controller + { + get => _controller; + set + { + _controller = value; + + if (_controller == -1) + { + _controller = 0; + } + + if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1) + { + ControllerType controller = Controllers[_controller].Type; + + IsLeft = true; + IsRight = true; + + switch (controller) + { + case ControllerType.Handheld: + ControllerImage = JoyConPairResource; + break; + case ControllerType.ProController: + ControllerImage = ProControllerResource; + break; + case ControllerType.JoyconPair: + ControllerImage = JoyConPairResource; + break; + case ControllerType.JoyconLeft: + ControllerImage = JoyConLeftResource; + IsRight = false; + break; + case ControllerType.JoyconRight: + ControllerImage = JoyConRightResource; + IsLeft = false; + break; + } + + LoadInputDriver(); + LoadProfiles(); + } + + OnPropertyChanged(); + NotifyChanges(); + } + } + + public string ControllerImage + { + get => _controllerImage; + set + { + _controllerImage = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(Image)); + } + } + + public SvgImage Image + { + get + { + SvgImage image = new(); + + if (!string.IsNullOrWhiteSpace(_controllerImage)) + { + SvgSource source = new(default(Uri)); + + source.Load(EmbeddedResources.GetStream(_controllerImage)); + + image.Source = source; + } + + return image; + } + } + + public string ProfileName + { + get => _profileName; set + { + _profileName = value; + + OnPropertyChanged(); + } + } + + public int Device + { + get => _device; + set + { + _device = value < 0 ? 0 : value; + + if (_device >= Devices.Count) + { + return; + } + + var selected = Devices[_device].Type; + + if (selected != DeviceType.None) + { + LoadControllers(); + + if (_isLoaded) + { + LoadConfiguration(LoadDefaultConfiguration()); + } + } + + OnPropertyChanged(); + NotifyChanges(); + } + } + + public InputConfig Config { get; set; } + + public ControllerInputViewModel(UserControl owner) : this() + { + if (Program.PreviewerDetached) + { + _mainWindow = + (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current + .ApplicationLifetime).MainWindow; + + AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); + + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + + _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); + + _isLoaded = false; + + LoadDevices(); + + PlayerId = PlayerIndex.Player1; + } + } + + public ControllerInputViewModel() + { + PlayerIndexes = new ObservableCollection<PlayerModel>(); + Controllers = new ObservableCollection<ControllerModel>(); + Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); + ProfilesList = new AvaloniaList<string>(); + DeviceList = new AvaloniaList<string>(); + + ControllerImage = ProControllerResource; + + PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); + PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); + PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); + PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); + PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); + PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); + PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); + PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); + PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); + } + + private void LoadConfiguration(InputConfig inputConfig = null) + { + Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); + + if (Config is StandardKeyboardInputConfig keyboardInputConfig) + { + Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig); + } + + if (Config is StandardControllerInputConfig controllerInputConfig) + { + Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig); + } + } + + public void LoadDevice() + { + if (Config == null || Config.Backend == InputBackendType.Invalid) + { + Device = 0; + } + else + { + var type = DeviceType.None; + + if (Config is StandardKeyboardInputConfig) + { + type = DeviceType.Keyboard; + } + + if (Config is StandardControllerInputConfig) + { + type = DeviceType.Controller; + } + + var item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); + if (item != default) + { + Device = Devices.ToList().FindIndex(x => x.Id == item.Id); + } + else + { + Device = 0; + } + } + } + + public async void ShowMotionConfig() + { + await MotionInputView.Show(this); + } + + public async void ShowRumbleConfig() + { + await RumbleInputView.Show(this); + } + + private void LoadInputDriver() + { + if (_device < 0) + { + return; + } + + string id = GetCurrentGamepadId(); + var type = Devices[Device].Type; + + if (type == DeviceType.None) + { + return; + } + + if (type == DeviceType.Keyboard) + { + if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) + { + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); + } + else + { + SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + } + } + else + { + SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + } + } + + private void HandleOnGamepadDisconnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private void HandleOnGamepadConnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private string GetCurrentGamepadId() + { + if (_device < 0) + { + return string.Empty; + } + + var device = Devices[Device]; + + if (device.Type == DeviceType.None) + { + return null; + } + + return device.Id.Split(" ")[0]; + } + + public void LoadControllers() + { + Controllers.Clear(); + + if (_playerId == PlayerIndex.Handheld) + { + Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); + + Controller = 0; + } + else + { + Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); + Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); + Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); + Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); + + if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) + { + Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); + } + else + { + Controller = 0; + } + } + } + + private static string GetShortGamepadName(string str) + { + const string Ellipsis = "..."; + const int MaxSize = 50; + + if (str.Length > MaxSize) + { + return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; + } + + return str; + } + + private static string GetShortGamepadId(string str) + { + const string Hyphen = "-"; + const int Offset = 1; + + return str[(str.IndexOf(Hyphen) + Offset)..]; + } + + public void LoadDevices() + { + lock (Devices) + { + Devices.Clear(); + DeviceList.Clear(); + Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); + + foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + + if (gamepad != null) + { + Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); + } + } + + foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + + if (gamepad != null) + { + if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) + { + _controllerNumber++; + } + + Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); + } + } + + _controllerNumber = 0; + + DeviceList.AddRange(Devices.Select(x => x.Name)); + Device = Math.Min(Device, DeviceList.Count); + } + } + + private string GetProfileBasePath() + { + string path = AppDataManager.ProfilesDirPath; + var type = Devices[Device == -1 ? 0 : Device].Type; + + if (type == DeviceType.Keyboard) + { + path = Path.Combine(path, KeyboardString); + } + else if (type == DeviceType.Controller) + { + path = Path.Combine(path, ControllerString); + } + + return path; + } + + private void LoadProfiles() + { + ProfilesList.Clear(); + + string basePath = GetProfileBasePath(); + + if (!Directory.Exists(basePath)) + { + Directory.CreateDirectory(basePath); + } + + ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); + + foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) + { + ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); + } + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; + } + } + + public InputConfig LoadDefaultConfiguration() + { + var activeDevice = Devices.FirstOrDefault(); + + if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) + { + activeDevice = Devices[Device]; + } + + InputConfig config; + if (activeDevice.Type == DeviceType.Keyboard) + { + string id = activeDevice.Id; + + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = id, + ControllerType = ControllerType.ProController, + LeftJoycon = new LeftJoyconCommonConfig<Key> + { + 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<Key> + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + RightJoycon = new RightJoyconCommonConfig<Key> + { + 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<Key> + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }; + } + else if (activeDevice.Type == DeviceType.Controller) + { + bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); + + string id = activeDevice.Id.Split(" ")[0]; + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = id, + ControllerType = ControllerType.ProController, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + }, + RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + }, + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false, + }, + }; + } + else + { + config = new InputConfig(); + } + + config.PlayerIndex = _playerId; + + return config; + } + + public async void LoadProfile() + { + if (Device == 0) + { + return; + } + + InputConfig config = null; + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + config = LoadDefaultConfiguration(); + } + else + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (!File.Exists(path)) + { + var index = ProfilesList.IndexOf(ProfileName); + if (index != -1) + { + ProfilesList.RemoveAt(index); + } + return; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); + } + catch (JsonException) { } + catch (InvalidOperationException) + { + Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); + + return; + } + } + + if (config != null) + { + _isLoaded = false; + + LoadConfiguration(config); + + LoadDevice(); + + _isLoaded = true; + + NotifyChanges(); + } + } + + public async void SaveProfile() + { + if (Device == 0) + { + return; + } + + if (Configuration == null) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); + + return; + } + + bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; + + if (validFileName) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + InputConfig config = null; + + if (IsKeyboard) + { + config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig(); + } + else if (IsController) + { + config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig(); + } + + config.ControllerType = Controllers[_controller].Type; + + string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); + + await File.WriteAllTextAsync(path, jsonString); + + LoadProfiles(); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + } + } + + public async void RemoveProfile() + { + if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) + { + return; + } + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (File.Exists(path)) + { + File.Delete(path); + } + + LoadProfiles(); + } + } + + public void Save() + { + IsModified = false; + + List<InputConfig> newConfig = new(); + + newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); + + newConfig.Remove(newConfig.Find(x => x == null)); + + if (Device == 0) + { + newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); + } + else + { + var device = Devices[Device]; + + if (device.Type == DeviceType.Keyboard) + { + var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>; + inputConfig.Id = device.Id; + } + else + { + var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>; + inputConfig.Id = device.Id.Split(" ")[0]; + } + + var config = !IsController + ? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig() + : (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig(); + config.ControllerType = Controllers[_controller].Type; + config.PlayerIndex = _playerId; + + int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); + if (i == -1) + { + newConfig.Add(config); + } + else + { + newConfig[i] = config; + } + } + + _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + // Atomically replace and signal input change. + // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. + ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public void NotifyChange(string property) + { + OnPropertyChanged(property); + } + + public void NotifyChanges() + { + OnPropertyChanged(nameof(Configuration)); + OnPropertyChanged(nameof(IsController)); + OnPropertyChanged(nameof(ShowSettings)); + OnPropertyChanged(nameof(IsKeyboard)); + OnPropertyChanged(nameof(IsRight)); + OnPropertyChanged(nameof(IsLeft)); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + + SelectedGamepad?.Dispose(); + + AvaloniaKeyboardDriver.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs new file mode 100644 index 00000000..2cd714f4 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -0,0 +1,340 @@ +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Application = Avalonia.Application; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class DownloadableContentManagerViewModel : BaseModel + { + private readonly List<DownloadableContentContainer> _downloadableContentContainerList; + private readonly string _downloadableContentJsonPath; + + private readonly VirtualFileSystem _virtualFileSystem; + private AvaloniaList<DownloadableContentModel> _downloadableContents = new(); + private AvaloniaList<DownloadableContentModel> _views = new(); + private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new(); + + private string _search; + private readonly ulong _titleId; + private readonly IStorageProvider _storageProvider; + + private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList<DownloadableContentModel> DownloadableContents + { + get => _downloadableContents; + set + { + _downloadableContents = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + } + + public AvaloniaList<DownloadableContentModel> Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents + { + get => _selectedDownloadableContents; + set + { + _selectedDownloadableContents = value; + OnPropertyChanged(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public string UpdateCount + { + get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); + } + + public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId) + { + _virtualFileSystem = virtualFileSystem; + + _titleId = titleId; + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + _storageProvider = desktop.MainWindow.StorageProvider; + } + + _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); + + try + { + _downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, _serializerContext.ListDownloadableContentContainer); + } + catch + { + Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize."); + _downloadableContentContainerList = new List<DownloadableContentContainer>(); + } + + LoadDownloadableContents(); + } + + private void LoadDownloadableContents() + { + foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList) + { + if (File.Exists(downloadableContentContainer.ContainerPath)) + { + using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); + + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + + _virtualFileSystem.ImportTickets(partitionFileSystem); + + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) + { + using UniqueRef<IFile> ncaFile = new(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); + if (nca != null) + { + var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), + downloadableContentContainer.ContainerPath, + downloadableContentNca.FullPath, + downloadableContentNca.Enabled); + + DownloadableContents.Add(content); + + if (content.Enabled) + { + SelectedDownloadableContents.Add(content); + } + + OnPropertyChanged(nameof(UpdateCount)); + } + } + } + } + + // NOTE: Save the list again to remove leftovers. + Save(); + Sort(); + } + + public void Sort() + { + DownloadableContents.AsObservableChangeSet() + .Filter(Filter) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + OnPropertyChanged(nameof(Views)); + } + + private bool Filter(object arg) + { + if (arg is DownloadableContentModel content) + { + return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + private Nca TryOpenNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(_virtualFileSystem.KeySet, ncaStorage); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadFileErrorMessage], ex.Message, containerPath)); + }); + } + + return null; + } + + public async void Add() + { + var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle], + AllowMultiple = true, + FileTypeFilter = new List<FilePickerFileType> + { + new("NSP") + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, + }); + + foreach (var file in result) + { + await AddDownloadableContent(file.Path.LocalPath); + } + } + + private async Task AddDownloadableContent(string path) + { + if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) + { + return; + } + + using FileStream containerFile = File.OpenRead(path); + + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + bool containsDownloadableContent = false; + + _virtualFileSystem.ImportTickets(partitionFileSystem); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef<IFile>(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path); + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId) + { + break; + } + + var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true); + DownloadableContents.Add(content); + SelectedDownloadableContents.Add(content); + + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + + containsDownloadableContent = true; + } + } + + if (!containsDownloadableContent) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]); + } + } + + public void Remove(DownloadableContentModel model) + { + DownloadableContents.Remove(model); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + + public void RemoveAll() + { + DownloadableContents.Clear(); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + + public void EnableAll() + { + SelectedDownloadableContents = new(DownloadableContents); + } + + public void DisableAll() + { + SelectedDownloadableContents.Clear(); + } + + public void Save() + { + _downloadableContentContainerList.Clear(); + + DownloadableContentContainer container = default; + + foreach (DownloadableContentModel downloadableContent in DownloadableContents) + { + if (container.ContainerPath != downloadableContent.ContainerPath) + { + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + container = new DownloadableContentContainer + { + ContainerPath = downloadableContent.ContainerPath, + DownloadableContentNcaList = new List<DownloadableContentNca>(), + }; + } + + container.DownloadableContentNcaList.Add(new DownloadableContentNca + { + Enabled = downloadableContent.Enabled, + TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16), + FullPath = downloadableContent.FullPath, + }); + } + + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer); + } + + } +} diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs new file mode 100644 index 00000000..17bd69b1 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,1708 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using DynamicData.Binding; +using LibHac.Common; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Models.Generic; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +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 SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Image = SixLabors.ImageSharp.Image; +using Key = Ryujinx.Input.Key; +using MissingKeyException = LibHac.Common.Keys.MissingKeyException; +using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class MainWindowViewModel : BaseModel + { + private const int HotKeyPressDelayMs = 500; + + private ObservableCollection<ApplicationData> _applications; + private string _aspectStatusText; + + private string _loadHeading; + private string _cacheLoadStatus; + private string _searchText; + private Timer _searchTimer; + private string _dockedStatusText; + private string _fifoStatusText; + private string _gameStatusText; + private string _volumeStatusText; + private string _gpuStatusText; + private bool _isAmiiboRequested; + private bool _isGameRunning; + private bool _isFullScreen; + private int _progressMaximum; + private int _progressValue; + private long _lastFullscreenToggle = Environment.TickCount64; + private bool _showLoadProgress; + private bool _showMenuAndStatusBar = true; + private bool _showStatusSeparator; + private Brush _progressBarForegroundColor; + private Brush _progressBarBackgroundColor; + private Brush _vsyncColor; + private byte[] _selectedIcon; + private bool _isAppletMenuActive; + private int _statusBarProgressMaximum; + private int _statusBarProgressValue; + private bool _isPaused; + private bool _showContent = true; + private bool _isLoadingIndeterminate = true; + private bool _showAll; + private string _lastScannedAmiiboId; + private bool _statusBarVisible; + private ReadOnlyObservableCollection<ApplicationData> _appsObservableList; + + private string _showUiKey = "F4"; + private string _pauseKey = "F5"; + private string _screenshotKey = "F8"; + private float _volume; + private float _volumeBeforeMute; + private string _backendText; + + private bool _canUpdate = true; + private Cursor _cursor; + private string _title; + private string _currentEmulatedGamePath; + private readonly AutoResetEvent _rendererWaitEvent; + private WindowState _windowState; + private double _windowWidth; + private double _windowHeight; + + private bool _isActive; + + public ApplicationData ListSelectedApplication; + public ApplicationData GridSelectedApplication; + + private string TitleName { get; set; } + internal AppHost AppHost { get; set; } + + public MainWindowViewModel() + { + Applications = new ObservableCollection<ApplicationData>(); + + Applications.ToObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out _appsObservableList).AsObservableList(); + + _rendererWaitEvent = new AutoResetEvent(false); + + if (Program.PreviewerDetached) + { + LoadConfigurableHotKeys(); + + Volume = ConfigurationState.Instance.System.AudioVolume; + } + } + + public void Initialize( + ContentManager contentManager, + IStorageProvider storageProvider, + ApplicationLibrary applicationLibrary, + VirtualFileSystem virtualFileSystem, + AccountManager accountManager, + InputManager inputManager, + UserChannelPersistence userChannelPersistence, + LibHacHorizonManager libHacHorizonManager, + IHostUIHandler uiHandler, + Action<bool> showLoading, + Action<bool> switchToGameControl, + Action<Control> setMainContent, + TopLevel topLevel) + { + ContentManager = contentManager; + StorageProvider = storageProvider; + ApplicationLibrary = applicationLibrary; + VirtualFileSystem = virtualFileSystem; + AccountManager = accountManager; + InputManager = inputManager; + UserChannelPersistence = userChannelPersistence; + LibHacHorizonManager = libHacHorizonManager; + UiHandler = uiHandler; + + ShowLoading = showLoading; + SwitchToGameControl = switchToGameControl; + SetMainContent = setMainContent; + TopLevel = topLevel; + } + + #region Properties + + public string SearchText + { + get => _searchText; + set + { + _searchText = value; + + _searchTimer?.Dispose(); + + _searchTimer = new Timer(TimerCallback, null, 1000, 0); + } + } + + private void TimerCallback(object obj) + { + RefreshView(); + + _searchTimer.Dispose(); + _searchTimer = null; + } + + public bool CanUpdate + { + get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false); + set + { + _canUpdate = value; + OnPropertyChanged(); + } + } + + public Cursor Cursor + { + get => _cursor; + set + { + _cursor = value; + OnPropertyChanged(); + } + } + + public ReadOnlyObservableCollection<ApplicationData> AppsObservableList + { + get => _appsObservableList; + set + { + _appsObservableList = value; + + OnPropertyChanged(); + } + } + + public bool IsPaused + { + get => _isPaused; + set + { + _isPaused = value; + + OnPropertyChanged(); + } + } + + public long LastFullscreenToggle + { + get => _lastFullscreenToggle; + set + { + _lastFullscreenToggle = value; + + OnPropertyChanged(); + } + } + + public bool StatusBarVisible + { + get => _statusBarVisible && EnableNonGameRunningControls; + set + { + _statusBarVisible = value; + + OnPropertyChanged(); + } + } + + public bool EnableNonGameRunningControls => !IsGameRunning; + + public bool ShowFirmwareStatus => !ShowLoadProgress; + + public bool IsGameRunning + { + get => _isGameRunning; + set + { + _isGameRunning = value; + + if (!value) + { + ShowMenuAndStatusBar = false; + } + + OnPropertyChanged(); + OnPropertyChanged(nameof(EnableNonGameRunningControls)); + OnPropertyChanged(nameof(IsAppletMenuActive)); + OnPropertyChanged(nameof(StatusBarVisible)); + OnPropertyChanged(nameof(ShowFirmwareStatus)); + } + } + + public bool IsAmiiboRequested + { + get => _isAmiiboRequested && _isGameRunning; + set + { + _isAmiiboRequested = value; + + OnPropertyChanged(); + } + } + + public bool ShowLoadProgress + { + get => _showLoadProgress; + set + { + _showLoadProgress = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowFirmwareStatus)); + } + } + + public string GameStatusText + { + get => _gameStatusText; + set + { + _gameStatusText = value; + + OnPropertyChanged(); + } + } + + public bool IsFullScreen + { + get => _isFullScreen; + set + { + _isFullScreen = value; + + OnPropertyChanged(); + } + } + + public bool ShowAll + { + get => _showAll; + set + { + _showAll = value; + + OnPropertyChanged(); + } + } + + public string LastScannedAmiiboId + { + get => _lastScannedAmiiboId; + set + { + _lastScannedAmiiboId = value; + + OnPropertyChanged(); + } + } + + public ApplicationData SelectedApplication + { + get + { + return Glyph switch + { + Glyph.List => ListSelectedApplication, + Glyph.Grid => GridSelectedApplication, + _ => null, + }; + } + } + + public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; + + public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; + + public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + + public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild; + + public string LoadHeading + { + get => _loadHeading; + set + { + _loadHeading = value; + + OnPropertyChanged(); + } + } + + public string CacheLoadStatus + { + get => _cacheLoadStatus; + set + { + _cacheLoadStatus = value; + + OnPropertyChanged(); + } + } + + public Brush ProgressBarBackgroundColor + { + get => _progressBarBackgroundColor; + set + { + _progressBarBackgroundColor = value; + + OnPropertyChanged(); + } + } + + public Brush ProgressBarForegroundColor + { + get => _progressBarForegroundColor; + set + { + _progressBarForegroundColor = value; + + OnPropertyChanged(); + } + } + + public Brush VsyncColor + { + get => _vsyncColor; + set + { + _vsyncColor = value; + + OnPropertyChanged(); + } + } + + public byte[] SelectedIcon + { + get => _selectedIcon; + set + { + _selectedIcon = value; + + OnPropertyChanged(); + } + } + + public int ProgressMaximum + { + get => _progressMaximum; + set + { + _progressMaximum = value; + + OnPropertyChanged(); + } + } + + public int ProgressValue + { + get => _progressValue; + set + { + _progressValue = value; + + OnPropertyChanged(); + } + } + + public int StatusBarProgressMaximum + { + get => _statusBarProgressMaximum; + set + { + _statusBarProgressMaximum = value; + + OnPropertyChanged(); + } + } + + public int StatusBarProgressValue + { + get => _statusBarProgressValue; + set + { + _statusBarProgressValue = value; + + OnPropertyChanged(); + } + } + + public string FifoStatusText + { + get => _fifoStatusText; + set + { + _fifoStatusText = value; + + OnPropertyChanged(); + } + } + + public string GpuNameText + { + get => _gpuStatusText; + set + { + _gpuStatusText = value; + + OnPropertyChanged(); + } + } + + public string BackendText + { + get => _backendText; + set + { + _backendText = value; + + OnPropertyChanged(); + } + } + + public string DockedStatusText + { + get => _dockedStatusText; + set + { + _dockedStatusText = value; + + OnPropertyChanged(); + } + } + + public string AspectRatioStatusText + { + get => _aspectStatusText; + set + { + _aspectStatusText = value; + + OnPropertyChanged(); + } + } + + public string VolumeStatusText + { + get => _volumeStatusText; + set + { + _volumeStatusText = value; + + OnPropertyChanged(); + } + } + + public bool VolumeMuted => _volume == 0; + + public float Volume + { + get => _volume; + set + { + _volume = value; + + if (_isGameRunning) + { + AppHost.Device.SetVolume(_volume); + } + + OnPropertyChanged(nameof(VolumeStatusText)); + OnPropertyChanged(nameof(VolumeMuted)); + OnPropertyChanged(); + } + } + + public float VolumeBeforeMute + { + get => _volumeBeforeMute; + set + { + _volumeBeforeMute = value; + + OnPropertyChanged(); + } + } + + public bool ShowStatusSeparator + { + get => _showStatusSeparator; + set + { + _showStatusSeparator = value; + + OnPropertyChanged(); + } + } + + public bool ShowMenuAndStatusBar + { + get => _showMenuAndStatusBar; + set + { + _showMenuAndStatusBar = value; + + OnPropertyChanged(); + } + } + + public bool IsLoadingIndeterminate + { + get => _isLoadingIndeterminate; + set + { + _isLoadingIndeterminate = value; + + OnPropertyChanged(); + } + } + + public bool IsActive + { + get => _isActive; + set + { + _isActive = value; + + OnPropertyChanged(); + } + } + + + public bool ShowContent + { + get => _showContent; + set + { + _showContent = value; + + OnPropertyChanged(); + } + } + + public bool IsAppletMenuActive + { + get => _isAppletMenuActive && EnableNonGameRunningControls; + set + { + _isAppletMenuActive = value; + + OnPropertyChanged(); + } + } + + public WindowState WindowState + { + get => _windowState; + internal set + { + _windowState = value; + + OnPropertyChanged(); + } + } + + public double WindowWidth + { + get => _windowWidth; + set + { + _windowWidth = value; + + OnPropertyChanged(); + } + } + + public double WindowHeight + { + get => _windowHeight; + set + { + _windowHeight = value; + + OnPropertyChanged(); + } + } + + public bool IsGrid => Glyph == Glyph.Grid; + public bool IsList => Glyph == Glyph.List; + + internal void Sort(bool isAscending) + { + IsAscending = isAscending; + + RefreshView(); + } + + internal void Sort(ApplicationSort sort) + { + SortMode = sort; + + RefreshView(); + } + + public bool StartGamesInFullscreen + { + get => ConfigurationState.Instance.UI.StartFullscreen; + set + { + ConfigurationState.Instance.UI.StartFullscreen.Value = value; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + OnPropertyChanged(); + } + } + + public bool ShowConsole + { + get => ConfigurationState.Instance.UI.ShowConsole; + set + { + ConfigurationState.Instance.UI.ShowConsole.Value = value; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + OnPropertyChanged(); + } + } + + public string Title + { + get => _title; + set + { + _title = value; + + OnPropertyChanged(); + } + } + + public bool ShowConsoleVisible + { + get => ConsoleHelper.SetConsoleWindowStateSupported; + } + + public bool ManageFileTypesVisible + { + get => FileAssociationHelper.IsTypeAssociationSupported; + } + + public ObservableCollection<ApplicationData> Applications + { + get => _applications; + set + { + _applications = value; + + OnPropertyChanged(); + } + } + + public Glyph Glyph + { + get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value; + set + { + ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsGrid)); + OnPropertyChanged(nameof(IsList)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public bool ShowNames + { + get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set + { + ConfigurationState.Instance.UI.ShowNames.Value = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(GridSizeScale)); + OnPropertyChanged(nameof(GridItemSelectorSize)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + internal ApplicationSort SortMode + { + get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value; + private set + { + ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(SortName)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public int ListItemSelectorSize + { + get + { + return ConfigurationState.Instance.UI.GridSize.Value switch + { + 1 => 78, + 2 => 100, + 3 => 120, + 4 => 140, + _ => 16, + }; + } + } + + public int GridItemSelectorSize + { + get + { + return ConfigurationState.Instance.UI.GridSize.Value switch + { + 1 => 120, + 2 => ShowNames ? 210 : 150, + 3 => ShowNames ? 240 : 180, + 4 => ShowNames ? 280 : 220, + _ => 16, + }; + } + } + + public int GridSizeScale + { + get => ConfigurationState.Instance.UI.GridSize; + set + { + ConfigurationState.Instance.UI.GridSize.Value = value; + + if (value < 2) + { + ShowNames = false; + } + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsGridSmall)); + OnPropertyChanged(nameof(IsGridMedium)); + OnPropertyChanged(nameof(IsGridLarge)); + OnPropertyChanged(nameof(IsGridHuge)); + OnPropertyChanged(nameof(ListItemSelectorSize)); + OnPropertyChanged(nameof(GridItemSelectorSize)); + OnPropertyChanged(nameof(ShowNames)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public string SortName + { + get + { + return SortMode switch + { + ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication], + ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper], + ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed], + ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed], + ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension], + ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize], + ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath], + ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], + _ => string.Empty, + }; + } + } + + public bool IsAscending + { + get => ConfigurationState.Instance.UI.IsAscendingOrder; + private set + { + ConfigurationState.Instance.UI.IsAscendingOrder.Value = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(SortMode)); + OnPropertyChanged(nameof(SortName)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public KeyGesture ShowUiKey + { + get => KeyGesture.Parse(_showUiKey); + set + { + _showUiKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public KeyGesture ScreenshotKey + { + get => KeyGesture.Parse(_screenshotKey); + set + { + _screenshotKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public KeyGesture PauseKey + { + get => KeyGesture.Parse(_pauseKey); set + { + _pauseKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public ContentManager ContentManager { get; private set; } + public IStorageProvider StorageProvider { get; private set; } + public ApplicationLibrary ApplicationLibrary { get; private set; } + public VirtualFileSystem VirtualFileSystem { get; private set; } + public AccountManager AccountManager { get; private set; } + public InputManager InputManager { get; private set; } + public UserChannelPersistence UserChannelPersistence { get; private set; } + public Action<bool> ShowLoading { get; private set; } + public Action<bool> SwitchToGameControl { get; private set; } + public Action<Control> SetMainContent { get; private set; } + public TopLevel TopLevel { get; private set; } + public RendererHost RendererHostControl { get; private set; } + public bool IsClosing { get; set; } + public LibHacHorizonManager LibHacHorizonManager { 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; + public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; + public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; + 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; + + #endregion + + #region PrivateMethods + + private IComparer<ApplicationData> GetComparer() + { + return SortMode switch + { +#pragma warning disable IDE0055 // Disable formatting + ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) + : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName), + ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) + : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer), + ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), + ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), + ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) + : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension), + ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize) + : SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize), + ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) + : SortExpressionComparer<ApplicationData>.Descending(app => app.Path), + ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) + : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite), + _ => null, +#pragma warning restore IDE0055 + }; + } + + public void RefreshView() + { + RefreshGrid(); + } + + private void RefreshGrid() + { + Applications.ToObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out _appsObservableList).AsObservableList(); + + OnPropertyChanged(nameof(AppsObservableList)); + } + + private bool Filter(object arg) + { + if (arg is ApplicationData app) + { + return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower()); + } + + return false; + } + + private async Task HandleFirmwareInstallation(string filename) + { + try + { + SystemVersion firmwareVersion = ContentManager.VerifyFirmwarePackage(filename); + + if (firmwareVersion == null) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); + + return; + } + + string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); + string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); + + SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); + if (currentVersion != null) + { + dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); + } + + dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + dialogTitle, + dialogMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); + + if (result == UserResult.Yes) + { + Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new(() => + { + Dispatcher.UIThread.InvokeAsync(delegate + { + waitingDialog.Show(); + }); + + try + { + ContentManager.InstallFirmware(filename); + + Dispatcher.UIThread.InvokeAsync(async delegate + { + waitingDialog.Close(); + + string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); + + await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + Logger.Info?.Print(LogClass.Application, message); + + // Purge Applet Cache. + + DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + + if (miiEditorCacheFolder.Exists) + { + miiEditorCacheFolder.Delete(true); + } + }); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(ex.Message); + }); + } + finally + { + RefreshFirmwareStatus(); + } + }) + { + Name = "GUI.FirmwareInstallerThread", + }; + + thread.Start(); + } + } + catch (MissingKeyException ex) + { + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + Logger.Error?.Print(LogClass.Application, ex.ToString()); + + await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); + } + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(ex.Message); + } + } + + private void ProgressHandler<T>(T state, int current, int total) where T : Enum + { + Dispatcher.UIThread.Post((() => + { + ProgressMaximum = total; + ProgressValue = current; + + switch (state) + { + case LoadState ptcState: + CacheLoadStatus = $"{current} / {total}"; + switch (ptcState) + { + case LoadState.Unloaded: + case LoadState.Loading: + LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingPPTC]; + IsLoadingIndeterminate = false; + break; + case LoadState.Loaded: + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); + IsLoadingIndeterminate = true; + CacheLoadStatus = ""; + break; + } + break; + case ShaderCacheLoadingState shaderCacheState: + CacheLoadStatus = $"{current} / {total}"; + switch (shaderCacheState) + { + case ShaderCacheLoadingState.Start: + case ShaderCacheLoadingState.Loading: + LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders]; + IsLoadingIndeterminate = false; + break; + case ShaderCacheLoadingState.Packaging: + LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders]; + IsLoadingIndeterminate = false; + break; + case ShaderCacheLoadingState.Loaded: + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); + IsLoadingIndeterminate = true; + CacheLoadStatus = ""; + break; + } + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } + })); + } + + private void PrepareLoadScreen() + { + using MemoryStream stream = new(SelectedIcon); + using var gameIconBmp = Image.Load<Bgra32>(stream); + + var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>(); + + const float ColorMultiple = 0.5f; + + Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B); + Color progressBgColor = Color.FromRgb( + (byte)(dominantColor.R * ColorMultiple), + (byte)(dominantColor.G * ColorMultiple), + (byte)(dominantColor.B * ColorMultiple)); + + ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); + ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); + } + + private void InitializeGame() + { + RendererHostControl.WindowCreated += RendererHost_Created; + + AppHost.StatusUpdatedEvent += Update_StatusBar; + AppHost.AppExit += AppHost_AppExit; + + _rendererWaitEvent.WaitOne(); + + AppHost?.Start(); + + AppHost?.DisposeContext(); + } + + private async Task HandleRelaunch() + { + if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) + { + UserChannelPersistence.ShouldRestart = false; + + await LoadApplication(_currentEmulatedGamePath); + } + else + { + // Otherwise, clear state. + UserChannelPersistence = new UserChannelPersistence(); + _currentEmulatedGamePath = null; + } + } + + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) + { + if (ShowMenuAndStatusBar && !ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Application.Current.Styles.TryGetResource(args.VSyncEnabled + ? "VsyncEnabled" + : "VsyncDisabled", + Application.Current.ActualThemeVariant, + out object color); + + if (color is not null) + { + VsyncColor = new SolidColorBrush((Color)color); + } + + DockedStatusText = args.DockedMode; + AspectRatioStatusText = args.AspectRatio; + GameStatusText = args.GameStatus; + VolumeStatusText = args.VolumeStatus; + FifoStatusText = args.FifoStatus; + GpuNameText = args.GpuName; + BackendText = args.GpuBackend; + + ShowStatusSeparator = true; + }); + } + } + + private void RendererHost_Created(object sender, EventArgs e) + { + ShowLoading(false); + + _rendererWaitEvent.Set(); + } + + #endregion + + #region PublicMethods + + public void SetUiProgressHandlers(Switch emulationContext) + { + if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) + { + emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; + } + + emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; + emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; + } + + public void LoadConfigurableHotKeys() + { + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey)) + { + ShowUiKey = new KeyGesture(showUiKey); + } + + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) + { + ScreenshotKey = new KeyGesture(screenshotKey); + } + + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) + { + PauseKey = new KeyGesture(pauseKey); + } + } + + public void TakeScreenshot() + { + AppHost.ScreenshotRequested = true; + } + + public void HideUi() + { + ShowMenuAndStatusBar = false; + } + + public void ToggleStartGamesInFullscreen() + { + StartGamesInFullscreen = !StartGamesInFullscreen; + } + + public void ToggleShowConsole() + { + ShowConsole = !ShowConsole; + } + + public void SetListMode() + { + Glyph = Glyph.List; + } + + public void SetGridMode() + { + Glyph = Glyph.Grid; + } + + public void SetAspectRatio(AspectRatio aspectRatio) + { + ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; + } + + public async Task InstallFirmwareFromFile() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = new List<FilePickerFileType> + { + new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes]) + { + Patterns = new[] { "*.xci", "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, + MimeTypes = new[] { "application/x-nx-xci", "application/zip" }, + }, + new("XCI") + { + Patterns = new[] { "*.xci" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, + MimeTypes = new[] { "application/x-nx-xci" }, + }, + new("ZIP") + { + Patterns = new[] { "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, + MimeTypes = new[] { "application/zip" }, + }, + }, + }); + + if (result.Count > 0) + { + await HandleFirmwareInstallation(result[0].Path.LocalPath); + } + } + + public async Task InstallFirmwareFromFolder() + { + var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + AllowMultiple = false, + }); + + if (result.Count > 0) + { + await HandleFirmwareInstallation(result[0].Path.LocalPath); + } + } + + public void OpenRyujinxFolder() + { + OpenHelper.OpenFolder(AppDataManager.BaseDirPath); + } + + public void OpenLogsFolder() + { + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) + { + OpenHelper.OpenFolder(logPath); + } + } + + public void ToggleDockMode() + { + if (IsGameRunning) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + } + + public async Task ExitCurrentState() + { + if (WindowState == WindowState.FullScreen) + { + ToggleFullscreen(); + } + else if (IsGameRunning) + { + await Task.Delay(100); + + AppHost?.ShowExitPrompt(); + } + } + + public static void ChangeLanguage(object languageCode) + { + LocaleManager.Instance.LoadLanguage((string)languageCode); + + if (Program.PreviewerDetached) + { + ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public async Task ManageProfiles() + { + await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); + } + + public void SimulateWakeUpMessage() + { + AppHost.Device.System.SimulateWakeUpMessage(); + } + + public async Task OpenFile() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], + AllowMultiple = false, + FileTypeFilter = new List<FilePickerFileType> + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" }, + AppleUniformTypeIdentifiers = new[] + { + "com.ryujinx.nsp", + "com.ryujinx.xci", + "com.ryujinx.nca", + "com.ryujinx.nro", + "com.ryujinx.nso", + }, + MimeTypes = new[] + { + "application/x-nx-nsp", + "application/x-nx-xci", + "application/x-nx-nca", + "application/x-nx-nro", + "application/x-nx-nso", + }, + }, + new("NSP") + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + new("XCI") + { + Patterns = new[] { "*.xci" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, + MimeTypes = new[] { "application/x-nx-xci" }, + }, + new("NCA") + { + Patterns = new[] { "*.nca" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, + MimeTypes = new[] { "application/x-nx-nca" }, + }, + new("NRO") + { + Patterns = new[] { "*.nro" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, + MimeTypes = new[] { "application/x-nx-nro" }, + }, + new("NSO") + { + Patterns = new[] { "*.nso" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, + MimeTypes = new[] { "application/x-nx-nso" }, + }, + }, + }); + + if (result.Count > 0) + { + await LoadApplication(result[0].Path.LocalPath); + } + } + + public async Task OpenFolder() + { + var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], + AllowMultiple = false, + }); + + if (result.Count > 0) + { + await LoadApplication(result[0].Path.LocalPath); + } + } + + public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "") + { + if (AppHost != null) + { + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage], + LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + return; + } + +#if RELEASE + await PerformanceCheck(); +#endif + + Logger.RestartTime(); + + SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language); + + PrepareLoadScreen(); + + RendererHostControl = new RendererHost(); + + AppHost = new AppHost( + RendererHostControl, + InputManager, + path, + VirtualFileSystem, + ContentManager, + AccountManager, + UserChannelPersistence, + this, + TopLevel); + + if (!await AppHost.LoadGuestApplication()) + { + AppHost.DisposeContext(); + AppHost = null; + + return; + } + + CanUpdate = false; + + LoadHeading = TitleName = titleName; + + if (string.IsNullOrWhiteSpace(titleName)) + { + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); + TitleName = AppHost.Device.Processes.ActiveApplication.Name; + } + + SwitchToRenderer(startFullscreen); + + _currentEmulatedGamePath = path; + + Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; + gameThread.Start(); + } + + public void SwitchToRenderer(bool startFullscreen) + { + Dispatcher.UIThread.Post(() => + { + SwitchToGameControl(startFullscreen); + + SetMainContent(RendererHostControl); + + RendererHostControl.Focus(); + }); + } + + public static void UpdateGameMetadata(string titleId) + { + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.UpdatePostGame(); + }); + } + + public void RefreshFirmwareStatus() + { + SystemVersion version = null; + try + { + version = ContentManager.GetCurrentFirmwareVersion(); + } + catch (Exception) { } + + bool hasApplet = false; + + if (version != null) + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); + + hasApplet = version.Major > 3; + } + else + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); + } + + IsAppletMenuActive = hasApplet; + } + + public void AppHost_AppExit(object sender, EventArgs e) + { + if (IsClosing) + { + return; + } + + IsGameRunning = false; + + Dispatcher.UIThread.InvokeAsync(async () => + { + ShowMenuAndStatusBar = true; + ShowContent = true; + ShowLoadProgress = false; + IsLoadingIndeterminate = false; + CanUpdate = true; + Cursor = Cursor.Default; + + SetMainContent(null); + + AppHost = null; + + await HandleRelaunch(); + }); + + RendererHostControl.WindowCreated -= RendererHost_Created; + RendererHostControl = null; + + SelectedIcon = null; + + Dispatcher.UIThread.InvokeAsync(() => + { + Title = $"Ryujinx {Program.Version}"; + }); + } + + public void ToggleFullscreen() + { + if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs) + { + return; + } + + LastFullscreenToggle = Environment.TickCount64; + + if (WindowState == WindowState.FullScreen) + { + WindowState = WindowState.Normal; + + if (IsGameRunning) + { + ShowMenuAndStatusBar = true; + } + } + else + { + WindowState = WindowState.FullScreen; + + if (IsGameRunning) + { + ShowMenuAndStatusBar = false; + } + } + + IsFullScreen = WindowState == WindowState.FullScreen; + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public static async Task PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableTrace.Value) + { + string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; + string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + mainMessage, + secondaryMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ConfigurationState.Instance.Logger.EnableTrace.Value = false; + + SaveConfig(); + } + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; + string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + mainMessage, + secondaryMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + + SaveConfig(); + } + } + } + #endregion + } +} diff --git a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs new file mode 100644 index 00000000..8321bf89 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs @@ -0,0 +1,336 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class ModManagerViewModel : BaseModel + { + private readonly string _modJsonPath; + + private AvaloniaList<ModModel> _mods = new(); + private AvaloniaList<ModModel> _views = new(); + private AvaloniaList<ModModel> _selectedMods = new(); + + private string _search; + private readonly ulong _applicationId; + private readonly IStorageProvider _storageProvider; + + private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList<ModModel> Mods + { + get => _mods; + set + { + _mods = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + } + + public AvaloniaList<ModModel> Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public AvaloniaList<ModModel> SelectedMods + { + get => _selectedMods; + set + { + _selectedMods = value; + OnPropertyChanged(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public string ModCount + { + get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); + } + + public ModManagerViewModel(ulong applicationId) + { + _applicationId = applicationId; + + _modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + _storageProvider = desktop.MainWindow.StorageProvider; + } + + LoadMods(applicationId); + } + + private void LoadMods(ulong applicationId) + { + Mods.Clear(); + SelectedMods.Clear(); + + string[] modsBasePaths = [ModLoader.GetSdModsBasePath(), ModLoader.GetModsBasePath()]; + + foreach (var path in modsBasePaths) + { + var inSd = path == ModLoader.GetSdModsBasePath(); + var modCache = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId); + + foreach (var mod in modCache.RomfsDirs) + { + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); + if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) + { + Mods.Add(modModel); + } + } + + foreach (var mod in modCache.RomfsContainers) + { + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); + } + + foreach (var mod in modCache.ExefsDirs) + { + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); + if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) + { + Mods.Add(modModel); + } + } + + foreach (var mod in modCache.ExefsContainers) + { + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); + } + } + + Sort(); + } + + public void Sort() + { + Mods.AsObservableChangeSet() + .Filter(Filter) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + + SelectedMods = new(Views.Where(x => x.Enabled)); + + OnPropertyChanged(nameof(ModCount)); + OnPropertyChanged(nameof(Views)); + OnPropertyChanged(nameof(SelectedMods)); + } + + private bool Filter(object arg) + { + if (arg is ModModel content) + { + return string.IsNullOrWhiteSpace(_search) || content.Name.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + public void Save() + { + ModMetadata modData = new(); + + foreach (ModModel mod in Mods) + { + modData.Mods.Add(new Mod + { + Name = mod.Name, + Path = mod.Path, + Enabled = SelectedMods.Contains(mod), + }); + } + + JsonHelper.SerializeToFile(_modJsonPath, modData, _serializerContext.ModMetadata); + } + + public void Delete(ModModel model) + { + var isSubdir = true; + var pathToDelete = model.Path; + var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath(); + var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16")); + + if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir) + { + isSubdir = false; + } + + if (isSubdir) + { + var parentDir = String.Empty; + + foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) + { + if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) + { + parentDir = dir; + break; + } + } + + if (parentDir == String.Empty) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogModDeleteNoParentMessage, + model.Path)); + }); + return; + } + } + + Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\""); + Directory.Delete(pathToDelete, true); + + Mods.Remove(model); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + + private void AddMod(DirectoryInfo directory) + { + string[] directories; + + try + { + directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories); + } + catch (Exception exception) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + exception.ToString(), + directory)); + }); + return; + } + + var destinationDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); + + // TODO: More robust checking for valid mod folders + var isDirectoryValid = true; + + if (directories.Length == 0) + { + isDirectoryValid = false; + } + + if (!isDirectoryValid) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogModInvalidMessage]); + }); + return; + } + + foreach (var dir in directories) + { + string dirToCreate = dir.Replace(directory.Parent.ToString(), destinationDir); + + // Mod already exists + if (Directory.Exists(dirToCreate)) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + LocaleManager.Instance[LocaleKeys.DialogModAlreadyExistsMessage], + dirToCreate)); + }); + + return; + } + + Directory.CreateDirectory(dirToCreate); + } + + var files = Directory.GetFiles(directory.ToString(), "*", SearchOption.AllDirectories); + + foreach (var file in files) + { + File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true); + } + + LoadMods(_applicationId); + } + + public async void Add() + { + var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.SelectModDialogTitle], + AllowMultiple = true, + }); + + foreach (var folder in result) + { + AddMod(new DirectoryInfo(folder.Path.LocalPath)); + } + } + + public void DeleteAll() + { + foreach (var mod in Mods) + { + Delete(mod); + } + + Mods.Clear(); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + + public void EnableAll() + { + SelectedMods = new(Mods); + } + + public void DisableAll() + { + SelectedMods.Clear(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs b/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs new file mode 100644 index 00000000..0b12a51f --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs @@ -0,0 +1,93 @@ +namespace Ryujinx.Ava.UI.ViewModels +{ + public class MotionInputViewModel : BaseModel + { + private int _slot; + public int Slot + { + get => _slot; + set + { + _slot = value; + OnPropertyChanged(); + } + } + + private int _altSlot; + public int AltSlot + { + get => _altSlot; + set + { + _altSlot = value; + OnPropertyChanged(); + } + } + + private string _dsuServerHost; + public string DsuServerHost + { + get => _dsuServerHost; + set + { + _dsuServerHost = value; + OnPropertyChanged(); + } + } + + private int _dsuServerPort; + public int DsuServerPort + { + get => _dsuServerPort; + set + { + _dsuServerPort = value; + OnPropertyChanged(); + } + } + + private bool _mirrorInput; + public bool MirrorInput + { + get => _mirrorInput; + set + { + _mirrorInput = value; + OnPropertyChanged(); + } + } + + private int _sensitivity; + public int Sensitivity + { + get => _sensitivity; + set + { + _sensitivity = value; + OnPropertyChanged(); + } + } + + private double _gryoDeadzone; + public double GyroDeadzone + { + get => _gryoDeadzone; + set + { + _gryoDeadzone = value; + OnPropertyChanged(); + } + } + + private bool _enableCemuHookMotion; + public bool EnableCemuHookMotion + { + get => _enableCemuHookMotion; + set + { + _enableCemuHookMotion = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs b/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs new file mode 100644 index 00000000..49de1993 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Ava.UI.ViewModels +{ + public class RumbleInputViewModel : BaseModel + { + private float _strongRumble; + public float StrongRumble + { + get => _strongRumble; + set + { + _strongRumble = value; + OnPropertyChanged(); + } + } + + private float _weakRumble; + public float WeakRumble + { + get => _weakRumble; + set + { + _weakRumble = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs new file mode 100644 index 00000000..bcaa0860 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -0,0 +1,614 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.GraphicsDriver; +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 System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class SettingsViewModel : BaseModel + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; + private TimeZoneContentManager _timeZoneContentManager; + + private readonly List<string> _validTzRegions; + + private readonly Dictionary<string, string> _networkInterfaces; + + private float _customResolutionScale; + private int _resolutionScale; + private int _graphicsBackendMultithreadingIndex; + private float _volume; + private bool _isVulkanAvailable = true; + private bool _directoryChanged; + private readonly List<string> _gpuIds = new(); + private KeyboardHotkeys _keyboardHotkeys; + private int _graphicsBackendIndex; + private int _scalingFilter; + private int _scalingFilterLevel; + + public event Action CloseWindow; + public event Action SaveSettingsEvent; + private int _networkInterfaceIndex; + private int _multiplayerModeIndex; + + public int ResolutionScale + { + get => _resolutionScale; + set + { + _resolutionScale = value; + + OnPropertyChanged(nameof(CustomResolutionScale)); + OnPropertyChanged(nameof(IsCustomResolutionScaleActive)); + } + } + + public int GraphicsBackendMultithreadingIndex + { + get => _graphicsBackendMultithreadingIndex; + set + { + _graphicsBackendMultithreadingIndex = value; + + if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) + ); + } + + OnPropertyChanged(); + } + } + + public float CustomResolutionScale + { + get => _customResolutionScale; + set + { + _customResolutionScale = MathF.Round(value, 1); + + OnPropertyChanged(); + } + } + + public bool IsVulkanAvailable + { + get => _isVulkanAvailable; + set + { + _isVulkanAvailable = value; + + OnPropertyChanged(); + } + } + + public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS(); + + public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + + public bool DirectoryChanged + { + get => _directoryChanged; + set + { + _directoryChanged = value; + + OnPropertyChanged(); + } + } + + public bool IsMacOS => OperatingSystem.IsMacOS(); + + public bool EnableDiscordIntegration { get; set; } + public bool CheckUpdatesOnStart { get; set; } + public bool ShowConfirmExit { get; set; } + public int HideCursor { get; set; } + public bool EnableDockedMode { get; set; } + public bool EnableKeyboard { get; set; } + public bool EnableMouse { get; set; } + public bool EnableVsync { get; set; } + public bool EnablePptc { get; set; } + public bool EnableInternetAccess { get; set; } + public bool EnableFsIntegrityChecks { get; set; } + public bool IgnoreMissingServices { get; set; } + public bool ExpandDramSize { get; set; } + public bool EnableShaderCache { get; set; } + public bool EnableTextureRecompression { get; set; } + public bool EnableMacroHLE { get; set; } + public bool EnableColorSpacePassthrough { get; set; } + public bool ColorSpacePassthroughAvailable => IsMacOS; + public bool EnableFileLog { get; set; } + public bool EnableStub { get; set; } + public bool EnableInfo { get; set; } + public bool EnableWarn { get; set; } + public bool EnableError { get; set; } + public bool EnableTrace { get; set; } + public bool EnableGuest { get; set; } + public bool EnableFsAccessLog { get; set; } + public bool EnableDebug { get; set; } + public bool IsOpenAlEnabled { get; set; } + public bool IsSoundIoEnabled { get; set; } + public bool IsSDL2Enabled { get; set; } + public bool IsCustomResolutionScaleActive => _resolutionScale == 4; + public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; + + public bool IsVulkanSelected => GraphicsBackendIndex == 0; + public bool UseHypervisor { get; set; } + + public string TimeZone { get; set; } + public string ShaderDumpPath { get; set; } + + public int Language { get; set; } + public int Region { get; set; } + public int FsGlobalAccessLogMode { get; set; } + public int AudioBackend { get; set; } + public int MaxAnisotropy { get; set; } + public int AspectRatio { get; set; } + public int AntiAliasingEffect { get; set; } + public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0"); + public int ScalingFilterLevel + { + get => _scalingFilterLevel; + set + { + _scalingFilterLevel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ScalingFilterLevelText)); + } + } + public int OpenglDebugLevel { get; set; } + public int MemoryMode { get; set; } + public int BaseStyleIndex { get; set; } + public int GraphicsBackendIndex + { + get => _graphicsBackendIndex; + set + { + _graphicsBackendIndex = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsVulkanSelected)); + } + } + public int ScalingFilter + { + get => _scalingFilter; + set + { + _scalingFilter = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsScalingFilterActive)); + } + } + + public int PreferredGpuIndex { get; set; } + + public float Volume + { + get => _volume; + set + { + _volume = value; + + ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100; + + OnPropertyChanged(); + } + } + + public DateTimeOffset CurrentDate { get; set; } + public TimeSpan CurrentTime { get; set; } + + internal AvaloniaList<TimeZone> TimeZones { get; set; } + public AvaloniaList<string> GameDirectories { get; set; } + public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; } + + public AvaloniaList<string> NetworkInterfaceList + { + get => new(_networkInterfaces.Keys); + } + + public AvaloniaList<string> MultiplayerModes + { + get => new(Enum.GetNames<MultiplayerMode>()); + } + + public KeyboardHotkeys KeyboardHotkeys + { + get => _keyboardHotkeys; + set + { + _keyboardHotkeys = value; + + OnPropertyChanged(); + } + } + + public int NetworkInterfaceIndex + { + get => _networkInterfaceIndex; + set + { + _networkInterfaceIndex = value != -1 ? value : 0; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]]; + } + } + + public int MultiplayerModeIndex + { + get => _multiplayerModeIndex; + set + { + _multiplayerModeIndex = value; + ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex; + } + } + + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() + { + _virtualFileSystem = virtualFileSystem; + _contentManager = contentManager; + if (Program.PreviewerDetached) + { + Task.Run(LoadTimeZones); + } + } + + public SettingsViewModel() + { + GameDirectories = new AvaloniaList<string>(); + TimeZones = new AvaloniaList<TimeZone>(); + AvailableGpus = new ObservableCollection<ComboBoxItem>(); + _validTzRegions = new List<string>(); + _networkInterfaces = new Dictionary<string, string>(); + + Task.Run(CheckSoundBackends); + Task.Run(PopulateNetworkInterfaces); + + if (Program.PreviewerDetached) + { + Task.Run(LoadAvailableGpus); + LoadCurrentConfiguration(); + } + } + + public async Task CheckSoundBackends() + { + IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; + IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; + IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; + + await Dispatcher.UIThread.InvokeAsync(() => + { + OnPropertyChanged(nameof(IsOpenAlEnabled)); + OnPropertyChanged(nameof(IsSoundIoEnabled)); + OnPropertyChanged(nameof(IsSDL2Enabled)); + }); + } + + private async Task LoadAvailableGpus() + { + AvailableGpus.Clear(); + + var devices = VulkanRenderer.GetPhysicalDevices(); + + if (devices.Length == 0) + { + IsVulkanAvailable = false; + GraphicsBackendIndex = 1; + } + else + { + foreach (var device in devices) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + _gpuIds.Add(device.Id); + + AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" }); + }); + } + } + + // GPU configuration needs to be loaded during the async method or it will always return 0. + PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ? + _gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0; + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex))); + } + + public async Task LoadTimeZones() + { + _timeZoneContentManager = new TimeZoneContentManager(); + + _timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None); + + foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets()) + { + int hours = Math.DivRem(offset, 3600, out int seconds); + int minutes = Math.Abs(seconds) / 60; + + string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr; + + await Dispatcher.UIThread.InvokeAsync(() => + { + TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2)); + + _validTzRegions.Add(location); + }); + } + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone))); + } + + private async Task PopulateNetworkInterfaces() + { + _networkInterfaces.Clear(); + _networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0"); + + foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + _networkInterfaces.Add(networkInterface.Name, networkInterface.Id); + }); + } + + // Network interface index needs to be loaded during the async method or it will always return 0. + NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex))); + } + + public void ValidateAndSetTimeZone(string location) + { + if (_validTzRegions.Contains(location)) + { + TimeZone = location; + } + } + + public void LoadCurrentConfiguration() + { + ConfigurationState config = ConfigurationState.Instance; + + // User Interface + EnableDiscordIntegration = config.EnableDiscordIntegration; + CheckUpdatesOnStart = config.CheckUpdatesOnStart; + ShowConfirmExit = config.ShowConfirmExit; + HideCursor = (int)config.HideCursor.Value; + + GameDirectories.Clear(); + GameDirectories.AddRange(config.UI.GameDirs.Value); + + BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1; + + // Input + EnableDockedMode = config.System.EnableDockedMode; + EnableKeyboard = config.Hid.EnableKeyboard; + EnableMouse = config.Hid.EnableMouse; + + // Keyboard Hotkeys + KeyboardHotkeys = config.Hid.Hotkeys.Value; + + // System + Region = (int)config.System.Region.Value; + Language = (int)config.System.Language.Value; + TimeZone = config.System.TimeZone; + + DateTime currentDateTime = DateTime.Now; + + CurrentDate = currentDateTime.Date; + CurrentTime = currentDateTime.TimeOfDay.Add(TimeSpan.FromSeconds(config.System.SystemTimeOffset)); + + EnableVsync = config.Graphics.EnableVsync; + EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; + ExpandDramSize = config.System.ExpandRam; + IgnoreMissingServices = config.System.IgnoreMissingServices; + + // CPU + EnablePptc = config.System.EnablePtc; + MemoryMode = (int)config.System.MemoryManagerMode.Value; + UseHypervisor = config.System.UseHypervisor; + + // Graphics + GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value; + // Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus(). + EnableShaderCache = config.Graphics.EnableShaderCache; + EnableTextureRecompression = config.Graphics.EnableTextureRecompression; + EnableMacroHLE = config.Graphics.EnableMacroHLE; + EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough; + ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1; + CustomResolutionScale = config.Graphics.ResScaleCustom; + MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy)); + AspectRatio = (int)config.Graphics.AspectRatio.Value; + GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value; + ShaderDumpPath = config.Graphics.ShadersDumpPath; + AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value; + ScalingFilter = (int)config.Graphics.ScalingFilter.Value; + ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value; + + // Audio + AudioBackend = (int)config.System.AudioBackend.Value; + Volume = config.System.AudioVolume * 100; + + // Network + EnableInternetAccess = config.System.EnableInternetAccess; + // LAN interface index is loaded asynchronously in PopulateNetworkInterfaces() + + // Logging + EnableFileLog = config.Logger.EnableFileLog; + EnableStub = config.Logger.EnableStub; + EnableInfo = config.Logger.EnableInfo; + EnableWarn = config.Logger.EnableWarn; + EnableError = config.Logger.EnableError; + EnableTrace = config.Logger.EnableTrace; + EnableGuest = config.Logger.EnableGuest; + EnableDebug = config.Logger.EnableDebug; + EnableFsAccessLog = config.Logger.EnableFsAccessLog; + FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; + OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; + + MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; + } + + public void SaveSettings() + { + ConfigurationState config = ConfigurationState.Instance; + + // User Interface + config.EnableDiscordIntegration.Value = EnableDiscordIntegration; + config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; + config.ShowConfirmExit.Value = ShowConfirmExit; + config.HideCursor.Value = (HideCursorMode)HideCursor; + + if (_directoryChanged) + { + List<string> gameDirs = new(GameDirectories); + config.UI.GameDirs.Value = gameDirs; + } + + config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; + + // Input + config.System.EnableDockedMode.Value = EnableDockedMode; + config.Hid.EnableKeyboard.Value = EnableKeyboard; + config.Hid.EnableMouse.Value = EnableMouse; + + // Keyboard Hotkeys + config.Hid.Hotkeys.Value = KeyboardHotkeys; + + // System + config.System.Region.Value = (Region)Region; + config.System.Language.Value = (Language)Language; + + if (_validTzRegions.Contains(TimeZone)) + { + config.System.TimeZone.Value = TimeZone; + } + + config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds()); + config.Graphics.EnableVsync.Value = EnableVsync; + config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; + config.System.ExpandRam.Value = ExpandDramSize; + config.System.IgnoreMissingServices.Value = IgnoreMissingServices; + + // CPU + config.System.EnablePtc.Value = EnablePptc; + config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; + config.System.UseHypervisor.Value = UseHypervisor; + + // Graphics + config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex; + config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); + config.Graphics.EnableShaderCache.Value = EnableShaderCache; + config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression; + config.Graphics.EnableMacroHLE.Value = EnableMacroHLE; + config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough; + config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1; + config.Graphics.ResScaleCustom.Value = CustomResolutionScale; + config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy); + config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio; + config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect; + config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter; + config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel; + + if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex) + { + DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off); + } + + config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex; + config.Graphics.ShadersDumpPath.Value = ShaderDumpPath; + + // Audio + AudioBackend audioBackend = (AudioBackend)AudioBackend; + if (audioBackend != config.System.AudioBackend.Value) + { + config.System.AudioBackend.Value = audioBackend; + + Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}"); + } + + config.System.AudioVolume.Value = Volume / 100; + + // Network + config.System.EnableInternetAccess.Value = EnableInternetAccess; + + // Logging + config.Logger.EnableFileLog.Value = EnableFileLog; + config.Logger.EnableStub.Value = EnableStub; + config.Logger.EnableInfo.Value = EnableInfo; + config.Logger.EnableWarn.Value = EnableWarn; + config.Logger.EnableError.Value = EnableError; + config.Logger.EnableTrace.Value = EnableTrace; + config.Logger.EnableGuest.Value = EnableGuest; + config.Logger.EnableDebug.Value = EnableDebug; + config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; + config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; + config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; + + config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; + config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; + + config.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + MainWindow.UpdateGraphicsConfig(); + + SaveSettingsEvent?.Invoke(); + + _directoryChanged = false; + } + + private static void RevertIfNotSaved() + { + Program.ReloadConfig(); + } + + public void ApplyButton() + { + SaveSettings(); + } + + public void OkButton() + { + SaveSettings(); + CloseWindow?.Invoke(); + } + + public void CancelButton() + { + RevertIfNotSaved(); + CloseWindow?.Invoke(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs new file mode 100644 index 00000000..5989ce09 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs @@ -0,0 +1,249 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Path = System.IO.Path; +using SpanHelpers = LibHac.Common.SpanHelpers; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class TitleUpdateViewModel : BaseModel + { + public TitleUpdateMetadata TitleUpdateWindowData; + public readonly string TitleUpdateJsonPath; + private VirtualFileSystem VirtualFileSystem { get; } + private ulong TitleId { get; } + + private AvaloniaList<TitleUpdateModel> _titleUpdates = new(); + private AvaloniaList<object> _views = new(); + private object _selectedUpdate; + + private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList<TitleUpdateModel> TitleUpdates + { + get => _titleUpdates; + set + { + _titleUpdates = value; + OnPropertyChanged(); + } + } + + public AvaloniaList<object> Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public object SelectedUpdate + { + get => _selectedUpdate; + set + { + _selectedUpdate = value; + OnPropertyChanged(); + } + } + + public IStorageProvider StorageProvider; + + public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId) + { + VirtualFileSystem = virtualFileSystem; + + TitleId = titleId; + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + StorageProvider = desktop.MainWindow.StorageProvider; + } + + TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); + + try + { + TitleUpdateWindowData = JsonHelper.DeserializeFromFile(TitleUpdateJsonPath, _serializerContext.TitleUpdateMetadata); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}"); + + TitleUpdateWindowData = new TitleUpdateMetadata + { + Selected = "", + Paths = new List<string>(), + }; + + Save(); + } + + LoadUpdates(); + } + + private void LoadUpdates() + { + foreach (string path in TitleUpdateWindowData.Paths) + { + AddUpdate(path); + } + + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == TitleUpdateWindowData.Selected, null); + + SelectedUpdate = selected; + + // NOTE: Save the list again to remove leftovers. + Save(); + SortUpdates(); + } + + public void SortUpdates() + { + var list = TitleUpdates.ToList(); + + list.Sort((first, second) => + { + if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) + { + return -1; + } + + if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) + { + return 1; + } + + return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; + }); + + Views.Clear(); + Views.Add(new BaseModel()); + Views.AddRange(list); + + if (SelectedUpdate == null) + { + SelectedUpdate = Views[0]; + } + else if (!TitleUpdates.Contains(SelectedUpdate)) + { + if (Views.Count > 1) + { + SelectedUpdate = Views[1]; + } + else + { + SelectedUpdate = Views[0]; + } + } + } + + private void AddUpdate(string path) + { + if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) + { + using FileStream file = new(path, FileMode.Open, FileAccess.Read); + + try + { + var pfs = new PartitionFileSystem(); + pfs.Initialize(file.AsStorage()).ThrowIfFailure(); + (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0); + + if (controlNca != null && patchNca != null) + { + ApplicationControlProperty controlData = new(); + + using UniqueRef<IFile> nacpFile = new(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + TitleUpdates.Add(new TitleUpdateModel(controlData, path)); + } + else + { + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage])); + } + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path))); + } + } + } + + public void RemoveUpdate(TitleUpdateModel update) + { + TitleUpdates.Remove(update); + + SortUpdates(); + } + + public async Task Add() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = true, + FileTypeFilter = new List<FilePickerFileType> + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, + }); + + foreach (var file in result) + { + AddUpdate(file.Path.LocalPath); + } + + SortUpdates(); + } + + public void Save() + { + TitleUpdateWindowData.Paths.Clear(); + TitleUpdateWindowData.Selected = ""; + + foreach (TitleUpdateModel update in TitleUpdates) + { + TitleUpdateWindowData.Paths.Add(update.Path); + + if (update == SelectedUpdate) + { + TitleUpdateWindowData.Selected = update.Path; + } + } + + JsonHelper.SerializeToFile(TitleUpdateJsonPath, TitleUpdateWindowData, _serializerContext.TitleUpdateMetadata); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs new file mode 100644 index 00000000..89b59122 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -0,0 +1,222 @@ +using Avalonia.Media; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.FileSystem; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using Color = Avalonia.Media.Color; + +namespace Ryujinx.Ava.UI.ViewModels +{ + internal class UserFirmwareAvatarSelectorViewModel : BaseModel + { + private static readonly Dictionary<string, byte[]> _avatarStore = new(); + + private ObservableCollection<ProfileImageModel> _images; + private Color _backgroundColor = Colors.White; + + private int _selectedIndex; + + public UserFirmwareAvatarSelectorViewModel() + { + _images = new ObservableCollection<ProfileImageModel>(); + + LoadImagesFromStore(); + } + + public Color BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + OnPropertyChanged(); + ChangeImageBackground(); + } + } + + public ObservableCollection<ProfileImageModel> Images + { + get => _images; + set + { + _images = value; + OnPropertyChanged(); + } + } + + public int SelectedIndex + { + get => _selectedIndex; + set + { + _selectedIndex = value; + + if (_selectedIndex == -1) + { + SelectedImage = null; + } + else + { + SelectedImage = _images[_selectedIndex].Data; + } + + OnPropertyChanged(); + } + } + + public byte[] SelectedImage { get; private set; } + + private void LoadImagesFromStore() + { + Images.Clear(); + + foreach (var image in _avatarStore) + { + Images.Add(new ProfileImageModel(image.Key, image.Value)); + } + } + + private void ChangeImageBackground() + { + foreach (var image in Images) + { + image.BackgroundColor = new SolidColorBrush(BackgroundColor); + } + } + + public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) + { + if (_avatarStore.Count > 0) + { + return; + } + + string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); + string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(avatarPath)) + { + using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); + + Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + foreach (DirectoryEntryEx item in romfs.EnumerateEntries()) + { + // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. + if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) + { + using var file = new UniqueRef<IFile>(); + + romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + using MemoryStream streamPng = new(); + + file.Get.AsStream().CopyTo(stream); + + stream.Position = 0; + + Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); + + avatarImage.SaveAsPng(streamPng); + + _avatarStore.Add(item.FullPath, streamPng.ToArray()); + } + } + } + } + + private static byte[] DecompressYaz0(Stream stream) + { + using BinaryReader reader = new(stream); + + reader.ReadInt32(); // Magic + + uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); + + reader.ReadInt64(); // Padding + + byte[] input = new byte[stream.Length - stream.Position]; + stream.Read(input, 0, input.Length); + + uint inputOffset = 0; + + byte[] output = new byte[decodedLength]; + uint outputOffset = 0; + + ushort mask = 0; + byte header = 0; + + while (outputOffset < decodedLength) + { + if ((mask >>= 1) == 0) + { + header = input[inputOffset++]; + mask = 0x80; + } + + if ((header & mask) != 0) + { + if (outputOffset == output.Length) + { + break; + } + + output[outputOffset++] = input[inputOffset++]; + } + else + { + byte byte1 = input[inputOffset++]; + byte byte2 = input[inputOffset++]; + + uint dist = (uint)((byte1 & 0xF) << 8) | byte2; + uint position = outputOffset - (dist + 1); + + uint length = (uint)byte1 >> 4; + if (length == 0) + { + length = (uint)input[inputOffset++] + 0x12; + } + else + { + length += 2; + } + + uint gap = outputOffset - position; + uint nonOverlappingLength = length; + + if (nonOverlappingLength > gap) + { + nonOverlappingLength = gap; + } + + Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength); + outputOffset += nonOverlappingLength; + position += nonOverlappingLength; + length -= nonOverlappingLength; + + while (length-- > 0) + { + output[outputOffset++] = output[position++]; + } + } + } + + return output; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs new file mode 100644 index 00000000..8e7d41a5 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Ava.UI.ViewModels +{ + internal class UserProfileImageSelectorViewModel : BaseModel + { + private bool _firmwareFound; + + public bool FirmwareFound + { + get => _firmwareFound; + + set + { + _firmwareFound = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs new file mode 100644 index 00000000..70274847 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs @@ -0,0 +1,28 @@ +using Microsoft.IdentityModel.Tokens; +using Ryujinx.Ava.UI.Models; +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class UserProfileViewModel : BaseModel, IDisposable + { + public UserProfileViewModel() + { + Profiles = new ObservableCollection<BaseModel>(); + LostProfiles = new ObservableCollection<UserProfile>(); + IsEmpty = LostProfiles.IsNullOrEmpty(); + } + + public ObservableCollection<BaseModel> Profiles { get; set; } + + public ObservableCollection<UserProfile> LostProfiles { get; set; } + + public bool IsEmpty { get; set; } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs new file mode 100644 index 00000000..85adef00 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs @@ -0,0 +1,117 @@ +using DynamicData; +using DynamicData.Binding; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class UserSaveManagerViewModel : BaseModel + { + private int _sortIndex; + private int _orderIndex; + private string _search; + private ObservableCollection<SaveModel> _saves = new(); + private ObservableCollection<SaveModel> _views = new(); + private readonly AccountManager _accountManager; + + public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); + + public int SortIndex + { + get => _sortIndex; + set + { + _sortIndex = value; + OnPropertyChanged(); + Sort(); + } + } + + public int OrderIndex + { + get => _orderIndex; + set + { + _orderIndex = value; + OnPropertyChanged(); + Sort(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public ObservableCollection<SaveModel> Saves + { + get => _saves; + set + { + _saves = value; + OnPropertyChanged(); + Sort(); + } + } + + public ObservableCollection<SaveModel> Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public UserSaveManagerViewModel(AccountManager accountManager) + { + _accountManager = accountManager; + } + + public void Sort() + { + Saves.AsObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + OnPropertyChanged(nameof(Views)); + } + + private bool Filter(object arg) + { + if (arg is SaveModel save) + { + return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + private IComparer<SaveModel> GetComparer() + { + return SortIndex switch + { + 0 => OrderIndex == 0 + ? SortExpressionComparer<SaveModel>.Ascending(save => save.Title) + : SortExpressionComparer<SaveModel>.Descending(save => save.Title), + 1 => OrderIndex == 0 + ? SortExpressionComparer<SaveModel>.Ascending(save => save.Size) + : SortExpressionComparer<SaveModel>.Descending(save => save.Size), + _ => null, + }; + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml new file mode 100644 index 00000000..99f2b6b6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -0,0 +1,1130 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + d:DesignHeight="800" + d:DesignWidth="800" + x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView" + x:DataType="viewModels:ControllerInputViewModel" + mc:Ignorable="d" + Focusable="True"> + <Design.DataContext> + <viewModels:ControllerInputViewModel /> + </Design.DataContext> + <UserControl.Resources> + <helpers:KeyValueConverter x:Key="Key" /> + </UserControl.Resources> + <UserControl.Styles> + <Style Selector="ToggleButton"> + <Setter Property="Width" Value="90" /> + <Setter Property="Height" Value="27" /> + <Setter Property="HorizontalAlignment" Value="Stretch" /> + </Style> + </UserControl.Styles> + <StackPanel + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Orientation="Vertical"> + <StackPanel + Margin="0 0 0 5" + Orientation="Vertical" + Spacing="5"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="10" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <!-- Player Selection --> + <Grid + Grid.Column="0" + Margin="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <TextBlock + Margin="5,0,10,0" + Width="90" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsPlayer}" /> + <ComboBox + Grid.Column="1" + Name="PlayerIndexBox" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + SelectionChanged="PlayerIndexBox_OnSelectionChanged" + ItemsSource="{Binding PlayerIndexes}" + SelectedIndex="{Binding PlayerId}"> + <ComboBox.ItemTemplate> + <DataTemplate> + <TextBlock Text="{Binding Name}" /> + </DataTemplate> + </ComboBox.ItemTemplate> + </ComboBox> + </Grid> + <!-- Profile Selection --> + <Grid + Grid.Column="2" + Margin="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <TextBlock + Margin="5,0,10,0" + Width="90" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsProfile}" /> + <ui:FAComboBox + Grid.Column="1" + IsEditable="True" + Name="ProfileBox" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + SelectedIndex="0" + ItemsSource="{Binding ProfilesList}" + Text="{Binding ProfileName, Mode=TwoWay}" /> + <Button + Grid.Column="2" + MinWidth="0" + Margin="5,0,0,0" + VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}" + Command="{ReflectionBinding LoadProfile}"> + <ui:SymbolIcon + Symbol="Upload" + FontSize="15" + Height="20" /> + </Button> + <Button + Grid.Column="3" + MinWidth="0" + Margin="5,0,0,0" + VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}" + Command="{ReflectionBinding SaveProfile}"> + <ui:SymbolIcon + Symbol="Save" + FontSize="15" + Height="20" /> + </Button> + <Button + Grid.Column="4" + MinWidth="0" + Margin="5,0,0,0" + VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}" + Command="{ReflectionBinding RemoveProfile}"> + <ui:SymbolIcon + Symbol="Delete" + FontSize="15" + Height="20" /> + </Button> + </Grid> + </Grid> + <Separator /> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="10" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <!-- Input Device --> + <Grid + Grid.Column="0" + Margin="2" + HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <TextBlock + Grid.Column="0" + Margin="5,0,10,0" + Width="90" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsInputDevice}" /> + <ComboBox + Grid.Column="1" + Name="DeviceBox" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + ItemsSource="{Binding DeviceList}" + SelectedIndex="{Binding Device}" /> + <Button + Grid.Column="2" + MinWidth="0" + Margin="5,0,0,0" + VerticalAlignment="Center" + Command="{ReflectionBinding LoadDevices}"> + <ui:SymbolIcon + Symbol="Refresh" + FontSize="15" + Height="20"/> + </Button> + </Grid> + <!-- Controller Type --> + <Grid + Grid.Column="2" + Margin="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <TextBlock + Margin="5,0,10,0" + Width="90" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsControllerType}" /> + <ComboBox + Grid.Column="1" + HorizontalAlignment="Stretch" + ItemsSource="{Binding Controllers}" + SelectedIndex="{Binding Controller}"> + <ComboBox.ItemTemplate> + <DataTemplate DataType="models:ControllerModel"> + <TextBlock Text="{Binding Name}" /> + </DataTemplate> + </ComboBox.ItemTemplate> + </ComboBox> + </Grid> + </Grid> + </StackPanel> + <!-- Button / JoyStick Settings --> + <Grid + Name="SettingButtons" + MinHeight="450" + FlowDirection="LeftToRight" + IsVisible="{Binding ShowSettings}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <!-- Left Controls --> + <StackPanel + Orientation="Vertical" + Margin="0,0,5,0" + Grid.Column="0"> + <!-- Left Triggers --> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + IsVisible="{Binding IsLeft}" + MinHeight="90" + CornerRadius="5"> + <Grid + Margin="10" + HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition /> + </Grid.RowDefinitions> + <StackPanel + Grid.Column="0" + Grid.Row="0" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsTriggerZL}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Grid.Column="0" + Grid.Row="1" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsTriggerL}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Grid.Column="1" + Grid.Row="1" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonMinus}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </Grid> + </Border> + <!-- Left Joystick --> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + IsVisible="{Binding IsLeft}" + Margin="0,5,0,0" + CornerRadius="5"> + <StackPanel + Margin="10" + Orientation="Vertical"> + <TextBlock + Margin="0,0,0,10" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsLStick}" /> + <!-- Left Joystick Keyboard --> + <StackPanel + IsVisible="{Binding !IsController}" + Orientation="Vertical"> + <!-- Left Joystick Button --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickButton}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left Joystick Up --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickUp}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftStickUp, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left Joystick Down --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickDown}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftStickDown, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left Joystick Left --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickLeft}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftStickLeft, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left Joystick Right --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickRight}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftStickRight, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + <!-- Left Joystick Controller --> + <StackPanel + IsVisible="{Binding IsController}" + Orientation="Vertical"> + <!-- Left Joystick Button --> + <StackPanel + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickButton}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left Joystick Stick --> + <StackPanel + Margin="0,4,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickStick}" + TextAlignment="Center" /> + <ToggleButton Tag="stick"> + <TextBlock + Text="{ReflectionBinding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <Separator + Margin="0,8,0,8" + Height="1" /> + <CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickX}"> + <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> + </CheckBox> + <CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickY}"> + <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> + </CheckBox> + <CheckBox IsChecked="{ReflectionBinding Configuration.LeftRotate90}"> + <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> + </CheckBox> + <Separator + Margin="0,8,0,8" + Height="1" /> + <StackPanel Orientation="Vertical"> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickDeadzone}" /> + <StackPanel + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <controls:SliderScroll + Width="130" + Maximum="1" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Minimum="0" + Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Width="25" + Text="{ReflectionBinding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" /> + </StackPanel> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickRange}" /> + <StackPanel + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <controls:SliderScroll + Width="130" + Maximum="2" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Minimum="0" + Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Width="25" + Text="{ReflectionBinding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" /> + </StackPanel> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + <!-- Left DPad --> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + VerticalAlignment="Top" + IsVisible="{Binding IsLeft}" + Margin="0,5,0,0" + CornerRadius="5"> + <StackPanel + Margin="10" + Orientation="Vertical"> + <TextBlock + Margin="0,0,0,10" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsDPad}" /> + <StackPanel Orientation="Vertical"> + <!-- Left DPad Up --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsDPadUp}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left DPad Down --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsDPadDown}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left DPad Left --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsDPadLeft}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Left DPad Right --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsDPadRight}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + </StackPanel> + <!-- Triggers & Side Buttons --> + <StackPanel + Grid.Column="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + MinHeight="90"> + <StackPanel + Margin="8" + Orientation="Vertical"> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsTriggerThreshold}" /> + <StackPanel + HorizontalAlignment="Center" + Orientation="Horizontal"> + <controls:SliderScroll + Width="130" + Maximum="1" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Minimum="0" + Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" /> + <TextBlock + Width="25" + Text="{ReflectionBinding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" /> + </StackPanel> + <StackPanel + Margin="0,4,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + IsVisible="{Binding !IsRight}" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsLeftSR}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Margin="0,4,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + IsVisible="{Binding !IsRight}" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsLeftSL}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.LeftButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Margin="0,4,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + IsVisible="{Binding !IsLeft}" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsRightSR}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Margin="0,4,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + IsVisible="{Binding !IsLeft}" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsRightSL}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + </Border> + <!-- Controller Picture --> + <Image + Margin="0,10,0,0" + MaxHeight="300" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Source="{Binding Image}" /> + <!-- Motion + Rumble --> + <StackPanel + Margin="0,10,0,0" + Spacing="5" + Orientation="Vertical" + VerticalAlignment="Bottom"> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + VerticalAlignment="Bottom" + HorizontalAlignment="Stretch" + IsVisible="{Binding IsController}"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <CheckBox + Margin="10" + MinWidth="0" + Grid.Column="0" + IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}"> + <TextBlock Text="{locale:Locale ControllerSettingsMotion}" /> + </CheckBox> + <Button + Margin="10" + Grid.Column="1" + Command="{Binding ShowMotionConfig}"> + <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> + </Button> + </Grid> + </Border> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + HorizontalAlignment="Stretch" + IsVisible="{Binding IsController}" + Margin="0,-1,0,0"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <CheckBox + Margin="10" + MinWidth="0" + Grid.Column="0" + IsChecked="{ReflectionBinding Configuration.EnableRumble, Mode=TwoWay}"> + <TextBlock Text="{locale:Locale ControllerSettingsRumble}" /> + </CheckBox> + <Button + Margin="10" + Grid.Column="1" + Command="{Binding ShowRumbleConfig}"> + <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> + </Button> + </Grid> + </Border> + </StackPanel> + </StackPanel> + <!-- Right Controls --> + <StackPanel + Orientation="Vertical" + Margin="5,0,0,0" + Grid.Column="2"> + <!-- Right Triggers --> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + IsVisible="{Binding IsRight}" + MinHeight="90" + CornerRadius="5"> + <Grid + Margin="10" + HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition /> + </Grid.RowDefinitions> + <StackPanel + Grid.Column="1" + Grid.Row="0" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsTriggerZR}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Grid.Column="1" + Grid.Row="1" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsTriggerR}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel + Grid.Column="0" + Grid.Row="1" + HorizontalAlignment="Right" + VerticalAlignment="Center" + Orientation="Horizontal"> + <TextBlock + Width="20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonPlus}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </Grid> + </Border> + <!-- Right Joystick --> + <Border + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + IsVisible="{Binding IsRight}" + Margin="0,5,0,0" + CornerRadius="5"> + <StackPanel + Margin="10" + Orientation="Vertical"> + <TextBlock + Margin="0,0,0,10" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtons}" /> + <StackPanel + Orientation="Vertical"> + <!-- Right Buttons A --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Width="120" + Margin="0,0,10,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonA}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Buttons B --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Width="120" + Margin="0,0,10,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonB}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Buttons X --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Width="120" + Margin="0,0,10,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonX}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Buttons Y --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Width="120" + Margin="0,0,10,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsButtonY}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + <!-- Right DPad --> + <Border + Padding="10" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + IsVisible="{Binding IsRight}" + Margin="0,5,0,0"> + <StackPanel Orientation="Vertical"> + <TextBlock + Margin="0,0,0,10" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsRStick}" /> + <!-- Right Joystick Keyboard --> + <StackPanel + IsVisible="{Binding !IsController}" + Orientation="Vertical"> + <!-- Right Joystick Button --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickButton}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Joystick Up --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickUp}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightStickUp, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Joystick Down --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickDown}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightStickDown, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Joystick Left --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickLeft}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightStickLeft, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Joystick Right --> + <StackPanel + Margin="0,0,0,4" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickRight}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightStickRight, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + <!-- Right Joystick Controller --> + <StackPanel + IsVisible="{Binding IsController}" + Orientation="Vertical"> + <!-- Right Joystick Button --> + <StackPanel + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickButton}" + TextAlignment="Center" /> + <ToggleButton> + <TextBlock + Text="{ReflectionBinding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <!-- Right Joystick Stick --> + <StackPanel + Margin="0,4,0,4" + Background="{DynamicResource ThemeDarkColor}" + Orientation="Horizontal"> + <TextBlock + Margin="0,0,10,0" + Width="120" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickStick}" + TextAlignment="Center" /> + <ToggleButton Tag="stick"> + <TextBlock + Text="{ReflectionBinding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <Separator Margin="0,8,0,8" Height="1" /> + <CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickX}"> + <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> + </CheckBox> + <CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickY}"> + <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> + </CheckBox> + <CheckBox IsChecked="{ReflectionBinding Configuration.RightRotate90}"> + <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> + </CheckBox> + <Separator Margin="0,8,0,8" Height="1" /> + <StackPanel Orientation="Vertical"> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickDeadzone}" /> + <StackPanel + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <controls:SliderScroll + Width="130" + Maximum="1" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Padding="0" + VerticalAlignment="Center" + Minimum="0" + Value="{ReflectionBinding Configuration.DeadzoneRight, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Width="25" + Text="{ReflectionBinding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" /> + </StackPanel> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsStickRange}" /> + <StackPanel + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <controls:SliderScroll + Width="130" + Maximum="2" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Minimum="0" + Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Width="25" + Text="{ReflectionBinding Configuration.RangeRight, StringFormat=\{0:0.00\}}" /> + </StackPanel> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + </StackPanel> + </Grid> + </StackPanel> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs new file mode 100644 index 00000000..35129706 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs @@ -0,0 +1,181 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using System; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class ControllerInputView : UserControl + { + private bool _dialogOpen; + + private ButtonKeyAssigner _currentAssigner; + internal ControllerInputViewModel ViewModel { get; set; } + + public ControllerInputView() + { + DataContext = ViewModel = new ControllerInputViewModel(this); + + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button && visual is not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) + { + _currentAssigner.Cancel(); + } + } + + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if ((bool)button.IsChecked) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + bool isStick = button.Tag != null && button.Tag.ToString() == "stick"; + + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); + + this.Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. + IButtonAssigner assigner = CreateButtonAssigner(isStick); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.IsAssigned) + { + ViewModel.IsModified = true; + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + ToggleButton oldButton = _currentAssigner.ToggledButton; + + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + else + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } + } + + public void SaveCurrentProfile() + { + ViewModel.Save(); + } + + private IButtonAssigner CreateButtonAssigner(bool forStick) + { + IButtonAssigner assigner; + + var device = ViewModel.Devices[ViewModel.Device]; + + if (device.Type == DeviceType.Keyboard) + { + assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad); + } + else if (device.Type == DeviceType.Controller) + { + assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick); + } + else + { + throw new Exception("Controller not supported"); + } + + return assigner; + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = false; + + if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed) + { + shouldUnbind = true; + } + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ViewModel.IsModified && !_dialogOpen) + { + _dialogOpen = true; + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage], + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Save(); + } + + _dialogOpen = false; + + ViewModel.IsModified = false; + + if (e.AddedItems.Count > 0) + { + var player = (PlayerModel)e.AddedItems[0]; + ViewModel.PlayerId = player.Id; + } + } + } + + public void Dispose() + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + ViewModel.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml new file mode 100644 index 00000000..a6b587f6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml @@ -0,0 +1,171 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" + x:DataType="viewModels:MotionInputViewModel" + Focusable="True"> + <Grid Margin="10"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </Grid.RowDefinitions> + <StackPanel Orientation="Vertical"> + <StackPanel + Orientation="Horizontal" + HorizontalAlignment="Center"> + <TextBlock + Margin="0" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" /> + <controls:SliderScroll + Margin="0,-5,0,-5" + Width="150" + MaxWidth="150" + TickFrequency="1" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Maximum="100" + Minimum="0" + Value="{Binding Sensitivity, Mode=TwoWay}" /> + <TextBlock + HorizontalAlignment="Center" + Margin="5, 0" + Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" /> + </StackPanel> + <StackPanel + Orientation="Horizontal" + HorizontalAlignment="Center"> + <TextBlock + Margin="0" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" /> + <controls:SliderScroll + Margin="0,-5,0,-5" + Width="150" + MaxWidth="150" + TickFrequency="1" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Maximum="100" + Minimum="0" + Value="{Binding GyroDeadzone, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Margin="5, 0" + Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" /> + </StackPanel> + <Separator + Height="1" + Margin="0,5" /> + <CheckBox + Margin="5" + IsChecked="{Binding EnableCemuHookMotion}"> + <TextBlock + Margin="0,3,0,0" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" /> + </CheckBox> + </StackPanel> + <Border + Grid.Row="1" + Padding="20,5" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1" + CornerRadius="5" + HorizontalAlignment="Stretch"> + <Grid VerticalAlignment="Top"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <StackPanel + Grid.Row="1" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Vertical"> + <StackPanel + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal"> + <TextBlock + Margin="5" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionServerHost}" /> + <TextBox + Height="30" + MinWidth="100" + MaxWidth="100" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding DsuServerHost, Mode=TwoWay}" /> + <TextBlock + Margin="5" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text=":" /> + <TextBox + Height="30" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding DsuServerPort, Mode=TwoWay}" /> + </StackPanel> + <StackPanel Orientation="Vertical"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <TextBlock + Margin="0,10,0,0" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionControllerSlot}" /> + <ui:NumberBox + Grid.Row="0" + Grid.Column="1" + Name="CemuHookSlotUpDown" + SmallChange="1" + LargeChange="1" + Maximum="4" + Minimum="0" + Value="{Binding Slot}" /> + <TextBlock + Margin="0,10,0,0" + Grid.Row="1" + Grid.Column="0" + VerticalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" /> + <ui:NumberBox + Grid.Row="1" + Grid.Column="1" + Name="CemuHookRightJoyConSlotUpDown" + SmallChange="1" + LargeChange="1" + Maximum="4" + Minimum="0" + Value="{Binding AltSlot}" /> + </Grid> + </StackPanel> + <CheckBox + HorizontalAlignment="Center" + IsChecked="{Binding MirrorInput, Mode=TwoWay}"> + <TextBlock + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsMotionMirrorInput}" /> + </CheckBox> + </StackPanel> + </Grid> + </Border> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs new file mode 100644 index 00000000..1b340752 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs @@ -0,0 +1,68 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid.Controller; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class MotionInputView : UserControl + { + private readonly MotionInputViewModel _viewModel; + + public MotionInputView() + { + InitializeComponent(); + } + + public MotionInputView(ControllerInputViewModel viewModel) + { + var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; + + _viewModel = new MotionInputViewModel + { + Slot = config.Slot, + AltSlot = config.AltSlot, + DsuServerHost = config.DsuServerHost, + DsuServerPort = config.DsuServerPort, + MirrorInput = config.MirrorInput, + Sensitivity = config.Sensitivity, + GyroDeadzone = config.GyroDeadzone, + EnableCemuHookMotion = config.EnableCemuHookMotion, + }; + + InitializeComponent(); + DataContext = _viewModel; + } + + public static async Task Show(ControllerInputViewModel viewModel) + { + MotionInputView content = new(viewModel); + + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], + Content = content, + }; + contentDialog.PrimaryButtonClick += (sender, args) => + { + var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; + config.Slot = content._viewModel.Slot; + config.Sensitivity = content._viewModel.Sensitivity; + config.GyroDeadzone = content._viewModel.GyroDeadzone; + config.AltSlot = content._viewModel.AltSlot; + config.DsuServerHost = content._viewModel.DsuServerHost; + config.DsuServerPort = content._viewModel.DsuServerPort; + config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion; + config.MirrorInput = content._viewModel.MirrorInput; + }; + + await contentDialog.ShowAsync(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml new file mode 100644 index 00000000..5b7087a4 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml @@ -0,0 +1,62 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" + x:DataType="viewModels:RumbleInputViewModel" + Focusable="True"> + <Grid Margin="10"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </Grid.RowDefinitions> + <StackPanel Orientation="Vertical"> + <StackPanel Orientation="Horizontal"> + <TextBlock + Width="100" + TextWrapping="WrapWithOverflow" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" /> + <controls:SliderScroll + Margin="0,-5,0,-5" + Width="200" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Maximum="10" + Minimum="0" + Value="{Binding StrongRumble, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Margin="5,0" + Text="{Binding StrongRumble, StringFormat=\{0:0.00\}}" /> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <TextBlock + Width="100" + TextWrapping="WrapWithOverflow" + HorizontalAlignment="Center" + Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" /> + <controls:SliderScroll + Margin="0,-5,0,-5" + Width="200" + MaxWidth="200" + Maximum="10" + TickFrequency="0.01" + IsSnapToTickEnabled="True" + SmallChange="0.01" + Minimum="0" + Value="{Binding WeakRumble, Mode=TwoWay}" /> + <TextBlock + VerticalAlignment="Center" + Margin="5,0" + Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" /> + </StackPanel> + </StackPanel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs new file mode 100644 index 00000000..9307f872 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs @@ -0,0 +1,58 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid.Controller; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class RumbleInputView : UserControl + { + private readonly RumbleInputViewModel _viewModel; + + public RumbleInputView() + { + InitializeComponent(); + } + + public RumbleInputView(ControllerInputViewModel viewModel) + { + var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; + + _viewModel = new RumbleInputViewModel + { + StrongRumble = config.StrongRumble, + WeakRumble = config.WeakRumble, + }; + + InitializeComponent(); + + DataContext = _viewModel; + } + + public static async Task Show(ControllerInputViewModel viewModel) + { + RumbleInputView content = new(viewModel); + + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], + Content = content, + }; + + contentDialog.PrimaryButtonClick += (sender, args) => + { + var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; + config.StrongRumble = content._viewModel.StrongRumble; + config.WeakRumble = content._viewModel.WeakRumble; + }; + + await contentDialog.ShowAsync(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml new file mode 100644 index 00000000..30358ada --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -0,0 +1,203 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + mc:Ignorable="d" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + x:DataType="viewModels:MainWindowViewModel" + x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView"> + <Design.DataContext> + <viewModels:MainWindowViewModel /> + </Design.DataContext> + <DockPanel HorizontalAlignment="Stretch"> + <Menu + Name="Menu" + Height="35" + Margin="0" + HorizontalAlignment="Left"> + <Menu.ItemsPanel> + <ItemsPanelTemplate> + <DockPanel Margin="0" HorizontalAlignment="Stretch" /> + </ItemsPanelTemplate> + </Menu.ItemsPanel> + <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}"> + <MenuItem + Command="{Binding OpenFile}" + Header="{locale:Locale MenuBarFileOpenFromFile}" + IsEnabled="{Binding EnableNonGameRunningControls}" + ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> + <MenuItem + Command="{Binding OpenFolder}" + Header="{locale:Locale MenuBarFileOpenUnpacked}" + IsEnabled="{Binding EnableNonGameRunningControls}" + ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> + <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}"> + <MenuItem + Click="OpenMiiApplet" + Header="Mii Edit Applet" + ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> + </MenuItem> + <Separator /> + <MenuItem + Command="{Binding OpenRyujinxFolder}" + Header="{locale:Locale MenuBarFileOpenEmuFolder}" + ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" /> + <MenuItem + Command="{Binding OpenLogsFolder}" + Header="{locale:Locale MenuBarFileOpenLogsFolder}" + ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" /> + <Separator /> + <MenuItem + Click="CloseWindow" + Header="{locale:Locale MenuBarFileExit}" + ToolTip.Tip="{locale:Locale ExitTooltip}" /> + </MenuItem> + <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}"> + <MenuItem + Padding="-10,0,0,0" + Command="{Binding ToggleFullscreen}" + Header="{locale:Locale MenuBarOptionsToggleFullscreen}" + InputGesture="F11" /> + <MenuItem + Padding="0" + Command="{Binding ToggleStartGamesInFullscreen}" + Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"> + <MenuItem.Icon> + <CheckBox + MinWidth="{DynamicResource CheckBoxSize}" + MinHeight="{DynamicResource CheckBoxSize}" + IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" + Padding="0" /> + </MenuItem.Icon> + <MenuItem.Styles> + <Style Selector="Viewbox#PART_IconPresenter"> + <Setter Property="MaxHeight" Value="36" /> + <Setter Property="MinHeight" Value="36" /> + <Setter Property="MaxWidth" Value="36" /> + <Setter Property="MinWidth" Value="36" /> + </Style> + <Style Selector="ContentPresenter#PART_HeaderPresenter"> + <Setter Property="Padding" Value="-10,0,0,0" /> + </Style> + </MenuItem.Styles> + </MenuItem> + <MenuItem + Padding="0" + IsVisible="{Binding ShowConsoleVisible}" + Command="{Binding ToggleShowConsole}" + Header="{locale:Locale MenuBarOptionsShowConsole}"> + <MenuItem.Icon> + <CheckBox + MinWidth="{DynamicResource CheckBoxSize}" + MinHeight="{DynamicResource CheckBoxSize}" + IsChecked="{Binding ShowConsole, Mode=TwoWay}" + Padding="0" /> + </MenuItem.Icon> + <MenuItem.Styles> + <Style Selector="Viewbox#PART_IconPresenter"> + <Setter Property="MaxHeight" Value="36" /> + <Setter Property="MinHeight" Value="36" /> + <Setter Property="MaxWidth" Value="36" /> + <Setter Property="MinWidth" Value="36" /> + </Style> + <Style Selector="ContentPresenter#PART_HeaderPresenter"> + <Setter Property="Padding" Value="-10,0,0,0" /> + </Style> + </MenuItem.Styles> + </MenuItem> + <Separator /> + <MenuItem + Name="ChangeLanguageMenuItem" + Padding="-10,0,0,0" + Header="{locale:Locale MenuBarOptionsChangeLanguage}" /> + <MenuItem + Name="ToggleFileTypesMenuItem" + Padding="-10,0,0,0" + Header="{locale:Locale MenuBarShowFileTypes}" /> + <Separator /> + <MenuItem + Click="OpenSettings" + Padding="-10,0,0,0" + Header="{locale:Locale MenuBarOptionsSettings}" + ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> + <MenuItem + Command="{Binding ManageProfiles}" + Padding="-10,0,0,0" + Header="{locale:Locale MenuBarOptionsManageUserProfiles}" + IsEnabled="{Binding EnableNonGameRunningControls}" + ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> + </MenuItem> + <MenuItem + Name="ActionsMenuItem" + VerticalAlignment="Center" + Header="{locale:Locale MenuBarActions}" + IsEnabled="{Binding IsGameRunning}"> + <MenuItem + Click="PauseEmulation_Click" + Header="{locale:Locale MenuBarOptionsPauseEmulation}" + InputGesture="{Binding PauseKey}" + IsEnabled="{Binding !IsPaused}" + IsVisible="{Binding !IsPaused}" /> + <MenuItem + Click="ResumeEmulation_Click" + Header="{locale:Locale MenuBarOptionsResumeEmulation}" + InputGesture="{Binding PauseKey}" + IsEnabled="{Binding IsPaused}" + IsVisible="{Binding IsPaused}" /> + <MenuItem + Click="StopEmulation_Click" + Header="{locale:Locale MenuBarOptionsStopEmulation}" + InputGesture="Escape" + IsEnabled="{Binding IsGameRunning}" + ToolTip.Tip="{locale:Locale StopEmulationTooltip}" /> + <MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" /> + <Separator /> + <MenuItem + Name="ScanAmiiboMenuItem" + AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree" + Click="OpenAmiiboWindow" + Header="{locale:Locale MenuBarActionsScanAmiibo}" + IsEnabled="{Binding IsAmiiboRequested}" /> + <MenuItem + Command="{Binding TakeScreenshot}" + Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" + InputGesture="{Binding ScreenshotKey}" + IsEnabled="{Binding IsGameRunning}" /> + <MenuItem + Command="{Binding HideUi}" + Header="{locale:Locale MenuBarFileToolsHideUi}" + InputGesture="{Binding ShowUiKey}" + IsEnabled="{Binding IsGameRunning}" /> + <MenuItem + Click="OpenCheatManagerForCurrentApp" + Header="{locale:Locale GameListContextMenuManageCheat}" + IsEnabled="{Binding IsGameRunning}" /> + </MenuItem> + <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}"> + <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}"> + <MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" /> + <MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" /> + </MenuItem> + <MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}"> + <MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/> + <MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/> + </MenuItem> + </MenuItem> + <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}"> + <MenuItem + Name="UpdateMenuItem" + IsEnabled="{Binding CanUpdate}" + Click="CheckForUpdates" + Header="{locale:Locale MenuBarHelpCheckForUpdates}" + ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> + <Separator /> + <MenuItem + Click="OpenAboutWindow" + Header="{locale:Locale MenuBarHelpAbout}" + ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> + </MenuItem> + </Menu> + </DockPanel> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs new file mode 100644 index 00000000..dc50ce26 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -0,0 +1,232 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +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 System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainMenuBarView : UserControl + { + public MainWindow Window { get; private set; } + public MainWindowViewModel ViewModel { get; private set; } + + public MainMenuBarView() + { + InitializeComponent(); + + ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems(); + ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems(); + } + + private CheckBox[] GenerateToggleFileTypeItems() + { + List<CheckBox> checkBoxes = new(); + + foreach (var item in Enum.GetValues(typeof(FileTypes))) + { + string fileName = Enum.GetName(typeof(FileTypes), item); + checkBoxes.Add(new CheckBox + { + Content = $".{fileName}", + IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes), + Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)), + }); + } + + return checkBoxes.ToArray(); + } + + private static MenuItem[] GenerateLanguageMenuItems() + { + List<MenuItem> menuItems = new(); + + string localePath = "Ryujinx/Assets/Locales"; + string localeExt = ".json"; + + string[] localesPath = EmbeddedResources.GetAllAvailableResources(localePath, localeExt); + + Array.Sort(localesPath); + + foreach (string locale in localesPath) + { + string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); + string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); + var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); + + if (!strings.TryGetValue("Language", out string languageName)) + { + languageName = languageCode; + } + + MenuItem menuItem = new() + { + Header = languageName, + Command = MiniCommand.Create(() => + { + MainWindowViewModel.ChangeLanguage(languageCode); + }), + }; + + menuItems.Add(menuItem); + } + + return menuItems.ToArray(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + Window = window; + } + + ViewModel = Window.ViewModel; + DataContext = ViewModel; + } + + private async void StopEmulation_Click(object sender, RoutedEventArgs e) + { + await Window.ViewModel.AppHost?.ShowExitPrompt(); + } + + private void PauseEmulation_Click(object sender, RoutedEventArgs e) + { + Window.ViewModel.AppHost?.Pause(); + } + + private void ResumeEmulation_Click(object sender, RoutedEventArgs e) + { + Window.ViewModel.AppHost?.Resume(); + } + + public async void OpenSettings(object sender, RoutedEventArgs e) + { + Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager); + + await Window.SettingsWindow.ShowDialog(Window); + + Window.SettingsWindow = null; + + ViewModel.LoadConfigurableHotKeys(); + } + + public async void OpenMiiApplet(object sender, RoutedEventArgs e) + { + string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + + if (!string.IsNullOrEmpty(contentPath)) + { + await ViewModel.LoadApplication(contentPath, false, "Mii Applet"); + } + } + + public async void OpenAmiiboWindow(object sender, RoutedEventArgs e) + { + if (!ViewModel.IsAmiiboRequested) + { + return; + } + + if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) + { + string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); + AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); + + await window.ShowDialog(Window); + + if (window.IsScanned) + { + ViewModel.ShowAll = window.ViewModel.ShowAllAmiibo; + ViewModel.LastScannedAmiiboId = window.ScannedAmiibo.GetId(); + + ViewModel.AppHost.Device.System.ScanAmiibo(deviceId, ViewModel.LastScannedAmiiboId, window.ViewModel.UseRandomUuid); + } + } + } + + public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) + { + if (!ViewModel.IsGameRunning) + { + return; + } + + string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); + + await new CheatWindow( + Window.VirtualFileSystem, + ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, + name, + Window.ViewModel.SelectedApplication.Path).ShowDialog(Window); + + ViewModel.AppHost.Device.EnableCheats(); + } + + private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + if (sender is MenuItem) + { + ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _); + } + } + + private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) + { + if (FileAssociationHelper.Install()) + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); + } + } + + private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) + { + if (FileAssociationHelper.Uninstall()) + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); + } + } + + public async void CheckForUpdates(object sender, RoutedEventArgs e) + { + if (Updater.CanUpdate(true)) + { + await Updater.BeginParse(Window, true); + } + } + + public async void OpenAboutWindow(object sender, RoutedEventArgs e) + { + await AboutWindow.Show(); + } + + public void CloseWindow(object sender, RoutedEventArgs e) + { + Window.Close(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml new file mode 100644 index 00000000..f9e192e6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml @@ -0,0 +1,289 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView" + x:DataType="viewModels:MainWindowViewModel"> + <Design.DataContext> + <viewModels:MainWindowViewModel /> + </Design.DataContext> + <Grid + Name="StatusBar" + Margin="0" + MinHeight="22" + HorizontalAlignment="Stretch" + VerticalAlignment="Bottom" + Background="{DynamicResource ThemeContentBackgroundColor}" + DockPanel.Dock="Bottom" + IsVisible="{Binding ShowMenuAndStatusBar}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <StackPanel + Grid.Column="0" + Margin="5" + VerticalAlignment="Center" + IsVisible="{Binding EnableNonGameRunningControls}"> + <Grid Margin="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Button + Width="25" + Height="25" + MinWidth="0" + Margin="0,0,5,0" + VerticalAlignment="Center" + Background="Transparent" + Click="Refresh_OnClick"> + <ui:SymbolIcon + Width="50" + Height="100" + Symbol="Refresh" /> + </Button> + <TextBlock + Name="LoadStatus" + Grid.Column="1" + Margin="0,0,5,0" + VerticalAlignment="Center" + IsVisible="{Binding EnableNonGameRunningControls}" + Text="{locale:Locale StatusBarGamesLoaded}" /> + <ProgressBar + Name="LoadProgressBar" + Grid.Column="2" + Height="6" + VerticalAlignment="Center" + Foreground="{DynamicResource SystemAccentColorLight2}" + IsVisible="{Binding StatusBarVisible}" + Maximum="{Binding StatusBarProgressMaximum}" + Value="{Binding StatusBarProgressValue}" /> + </Grid> + </StackPanel> + <StackPanel + Grid.Column="1" + Margin="0,2" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding IsGameRunning}" + MaxHeight="18" + Orientation="Horizontal"> + <TextBlock + Name="VsyncStatus" + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Foreground="{Binding VsyncColor}" + IsVisible="{Binding !ShowLoadProgress}" + PointerReleased="VsyncStatus_PointerReleased" + Text="VSync" + TextAlignment="Start" /> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <TextBlock + Name="DockedStatus" + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + PointerReleased="DockedStatus_PointerReleased" + Text="{Binding DockedStatusText}" + TextAlignment="Start" /> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <SplitButton + Name="AspectRatioStatus" + Padding="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Background="Transparent" + BorderThickness="0" + CornerRadius="0" + IsVisible="{Binding !ShowLoadProgress}" + Content="{Binding AspectRatioStatusText}" + Click="AspectRatioStatus_OnClick" + ToolTip.Tip="{locale:Locale AspectRatioTooltip}"> + <SplitButton.Styles> + <Style Selector="Border#SeparatorBorder"> + <Setter Property="Opacity" Value="0" /> + </Style> + </SplitButton.Styles> + <SplitButton.Flyout> + <MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/> + <MenuItem + Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" + Command="{Binding SetAspectRatio}" + CommandParameter="{x:Static config:AspectRatio.Stretched}"/> + </MenuFlyout> + </SplitButton.Flyout> + </SplitButton> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <ToggleSplitButton + Name="VolumeStatus" + Padding="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + VerticalContentAlignment="Center" + Content="{Binding VolumeStatusText}" + IsChecked="{Binding VolumeMuted}" + IsVisible="{Binding !ShowLoadProgress}" + PointerWheelChanged="VolumeStatus_OnPointerWheelChanged" + Background="Transparent" + BorderThickness="0" + CornerRadius="0"> + <ToggleSplitButton.Styles> + <Style Selector=":checked"> + <Style Selector="^:checked ContentPresenter"> + <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" /> + </Style> + </Style> + <Style Selector="Border#SeparatorBorder"> + <Setter Property="Opacity" Value="0" /> + </Style> + </ToggleSplitButton.Styles> + <ToggleSplitButton.Flyout> + <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> + <Grid Margin="0"> + <controls:SliderScroll + MaxHeight="40" + Width="150" + Margin="0" + Padding="0" + IsSnapToTickEnabled="True" + LargeChange="0.05" + Maximum="1" + Minimum="0" + SmallChange="0.01" + TickFrequency="0.05" + ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" + Value="{Binding Volume}" /> + </Grid> + </Flyout> + </ToggleSplitButton.Flyout> + </ToggleSplitButton> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <TextBlock + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + Text="{Binding GameStatusText}" + TextAlignment="Start" /> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <TextBlock + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + Text="{Binding FifoStatusText}" + TextAlignment="Start" /> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <TextBlock + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + Text="{Binding BackendText}" + TextAlignment="Start" /> + <Border + Width="2" + Height="12" + Margin="0" + BorderBrush="Gray" + Background="Gray" + BorderThickness="1" + IsVisible="{Binding !ShowLoadProgress}" /> + <TextBlock + Margin="5,0,5,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + Text="{Binding GpuNameText}" + TextAlignment="Start" /> + </StackPanel> + <StackPanel + Grid.Column="3" + Margin="0,0,5,0" + VerticalAlignment="Center" + IsVisible="{Binding ShowFirmwareStatus}" + Orientation="Horizontal"> + <TextBlock + Name="FirmwareStatus" + Margin="0" + HorizontalAlignment="Right" + VerticalAlignment="Center" + Text="{locale:Locale StatusBarSystemVersion}" /> + </StackPanel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs new file mode 100644 index 00000000..239a7cbf --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs @@ -0,0 +1,72 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainStatusBarView : UserControl + { + public MainWindow Window; + + public MainStatusBarView() + { + InitializeComponent(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + Window = window; + } + + DataContext = Window.ViewModel; + } + + private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + Window.ViewModel.AppHost.Device.EnableDeviceVsync = !Window.ViewModel.AppHost.Device.EnableDeviceVsync; + + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); + } + + private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + + private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e) + { + AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; + ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; + } + + private void Refresh_OnClick(object sender, RoutedEventArgs e) + { + Window.LoadApplications(); + } + + private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e) + { + // Change the volume by 5% at a time + float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f; + + Window.ViewModel.Volume = newValue switch + { + < 0 => 0, + > 1 => 1, + _ => newValue, + }; + + e.Handled = true; + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml new file mode 100644 index 00000000..cc21b5c6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml @@ -0,0 +1,177 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="Ryujinx.Ava.UI.Views.Main.MainViewControls" + x:DataType="viewModels:MainWindowViewModel"> + <Design.DataContext> + <viewModels:MainWindowViewModel /> + </Design.DataContext> + <DockPanel + Margin="0,0,0,5" + Height="35" + HorizontalAlignment="Stretch"> + <Button + Width="40" + MinWidth="40" + Margin="5,2,0,2" + VerticalAlignment="Stretch" + Command="{Binding SetListMode}" + IsEnabled="{Binding IsGrid}"> + <ui:FontIcon + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + FontFamily="avares://FluentAvalonia/Fonts#Symbols" + Glyph="{helpers:GlyphValueConverter List}" /> + </Button> + <Button + Width="40" + MinWidth="40" + Margin="5,2,5,2" + VerticalAlignment="Stretch" + Command="{Binding SetGridMode}" + IsEnabled="{Binding IsList}"> + <ui:FontIcon + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + FontFamily="avares://FluentAvalonia/Fonts#Symbols" + Glyph="{helpers:GlyphValueConverter Grid}" /> + </Button> + <TextBlock + Margin="10,0" + VerticalAlignment="Center" + Text="{locale:Locale IconSize}" + ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> + <controls:SliderScroll + Width="150" + Height="35" + Margin="5,-10,5,0" + VerticalAlignment="Center" + IsSnapToTickEnabled="True" + SmallChange="1" + Maximum="4" + Minimum="1" + TickFrequency="1" + ToolTip.Tip="{locale:Locale IconSizeTooltip}" + Value="{Binding GridSizeScale}" /> + <CheckBox + Margin="0" + VerticalAlignment="Center" + IsChecked="{Binding ShowNames, Mode=TwoWay}" + IsVisible="{Binding IsGrid}"> + <TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" /> + </CheckBox> + <TextBox + Name="SearchBox" + MinWidth="200" + Margin="5,0,5,0" + HorizontalAlignment="Right" + VerticalAlignment="Center" + DockPanel.Dock="Right" + KeyUp="SearchBox_OnKeyUp" + Text="{Binding SearchText}" + Watermark="{locale:Locale MenuSearch}" /> + <DropDownButton + Width="150" + HorizontalAlignment="Right" + VerticalAlignment="Center" + Content="{Binding SortName}" + DockPanel.Dock="Right"> + <DropDownButton.Flyout> + <Flyout Placement="Bottom"> + <StackPanel + Margin="0" + HorizontalAlignment="Stretch" + Orientation="Vertical"> + <StackPanel> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale CommonFavorite}" + GroupName="Sort" + IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}" + Tag="Favorite" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderApplication}" + GroupName="Sort" + IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" + Tag="Title" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderDeveloper}" + GroupName="Sort" + IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}" + Tag="Developer" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderTimePlayed}" + GroupName="Sort" + IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}" + Tag="TotalTimePlayed" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderLastPlayed}" + GroupName="Sort" + IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}" + Tag="LastPlayed" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderFileExtension}" + GroupName="Sort" + IsChecked="{Binding IsSortedByType, Mode=OneTime}" + Tag="FileType" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderFileSize}" + GroupName="Sort" + IsChecked="{Binding IsSortedBySize, Mode=OneTime}" + Tag="FileSize" /> + <RadioButton + Checked="Sort_Checked" + Content="{locale:Locale GameListHeaderPath}" + GroupName="Sort" + IsChecked="{Binding IsSortedByPath, Mode=OneTime}" + Tag="Path" /> + </StackPanel> + <Border + Width="60" + Height="2" + Margin="5" + HorizontalAlignment="Stretch" + BorderBrush="White" + BorderThickness="0,1,0,0"> + <Separator Height="0" HorizontalAlignment="Stretch" /> + </Border> + <RadioButton + Checked="Order_Checked" + Content="{locale:Locale OrderAscending}" + GroupName="Order" + IsChecked="{Binding IsAscending, Mode=OneTime}" + Tag="Ascending" /> + <RadioButton + Checked="Order_Checked" + Content="{locale:Locale OrderDescending}" + GroupName="Order" + IsChecked="{Binding !IsAscending, Mode=OneTime}" + Tag="Descending" /> + </StackPanel> + </Flyout> + </DropDownButton.Flyout> + </DropDownButton> + <TextBlock + Margin="10,0" + HorizontalAlignment="Right" + VerticalAlignment="Center" + DockPanel.Dock="Right" + Text="{locale:Locale CommonSort}" /> + </DockPanel> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs new file mode 100644 index 00000000..02fd1bf5 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs @@ -0,0 +1,54 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using System; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainViewControls : UserControl + { + public MainWindowViewModel ViewModel; + + public MainViewControls() + { + InitializeComponent(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + ViewModel = window.ViewModel; + } + + DataContext = ViewModel; + } + + public void Sort_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString())); + } + } + + public void Order_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + ViewModel.Sort(button.Tag.ToString() != "Descending"); + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) + { + ViewModel.SearchText = SearchBox.Text; + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml new file mode 100644 index 00000000..657e07ee --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml @@ -0,0 +1,81 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsAudioView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="AudioPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" /> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemAudioBackend}" + ToolTip.Tip="{locale:Locale AudioBackendTooltip}" + Width="250" /> + <ComboBox SelectedIndex="{Binding AudioBackend}" + Width="350" + HorizontalContentAlignment="Left"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" /> + </ComboBoxItem> + <ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}"> + <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" /> + </ComboBoxItem> + <ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}"> + <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" /> + </ComboBoxItem> + <ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}"> + <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemAudioVolume}" + ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" + Width="250" /> + <ui:NumberBox Value="{Binding Volume}" + ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" + Width="350" + SmallChange="1" + LargeChange="10" + SimpleNumberFormat="F0" + SpinButtonPlacementMode="Inline" + Minimum="0" + Maximum="100" /> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <controls:SliderScroll Value="{Binding Volume}" + Margin="250,0,0,0" + ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" + Minimum="0" + Maximum="100" + SmallChange="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + LargeChange="10" + Width="350" /> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs new file mode 100644 index 00000000..b672a0f2 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsAudioView : UserControl + { + public SettingsAudioView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml new file mode 100644 index 00000000..c74d3dd5 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml @@ -0,0 +1,77 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="CpuPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" /> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical"> + <CheckBox IsChecked="{Binding EnablePptc}"> + <TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}" + ToolTip.Tip="{locale:Locale PptcToggleTooltip}" /> + </CheckBox> + </StackPanel> + <Separator Height="1" /> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" /> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemMemoryManagerMode}" + ToolTip.Tip="{locale:Locale MemoryManagerTooltip}" + Width="250" /> + <ComboBox SelectedIndex="{Binding MemoryMode}" + ToolTip.Tip="{locale:Locale MemoryManagerTooltip}" + HorizontalContentAlignment="Left" + Width="350"> + <ComboBoxItem + ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}"> + <TextBlock + Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" /> + </ComboBoxItem> + <ComboBoxItem + ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" /> + </ComboBoxItem> + <ComboBoxItem + ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}"> + <TextBlock + Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <CheckBox IsChecked="{Binding UseHypervisor}" + IsVisible="{Binding IsHypervisorAvailable}" + ToolTip.Tip="{locale:Locale UseHypervisorTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabSystemUseHypervisor}" + ToolTip.Tip="{locale:Locale UseHypervisorTooltip}" /> + </CheckBox> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs new file mode 100644 index 00000000..a475971a --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsCPUView : UserControl + { + public SettingsCPUView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml new file mode 100644 index 00000000..22449478 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml @@ -0,0 +1,301 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + Design.Width="1000" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="GraphicsPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" /> + <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}" + Text="{locale:Locale SettingsTabGraphicsBackend}" + Width="250" /> + <ComboBox Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}" + SelectedIndex="{Binding GraphicsBackendIndex}"> + <ComboBoxItem IsVisible="{Binding IsVulkanAvailable}"> + <TextBlock Text="Vulkan" /> + </ComboBoxItem> + <ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}"> + <TextBlock Text="OpenGL" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}" + Text="{locale:Locale SettingsTabGraphicsPreferredGpu}" + Width="250" /> + <ComboBox Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}" + SelectedIndex="{Binding PreferredGpuIndex}" + ItemsSource="{Binding AvailableGpus}"/> + </StackPanel> + </StackPanel> + <Separator Height="1" /> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" /> + <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10"> + <StackPanel Orientation="Vertical"> + <CheckBox IsChecked="{Binding EnableShaderCache}" + ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableTextureRecompression}" + ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}"> + <TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableMacroHLE}" + ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}"> + <TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableColorSpacePassthrough}" + IsVisible="{Binding ColorSpacePassthroughAvailable}" + ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}"> + <TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" /> + </CheckBox> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}" + Text="{locale:Locale SettingsTabGraphicsResolutionScale}" + Width="250" /> + <ComboBox SelectedIndex="{Binding ResolutionScale}" + Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" /> + </ComboBoxItem> + </ComboBox> + <ui:NumberBox + Margin="10,0,0,0" + ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}" + MinWidth="150" + SmallChange="0.1" + LargeChange="1" + SimpleNumberFormat="F2" + SpinButtonPlacementMode="Inline" + IsVisible="{Binding IsCustomResolutionScaleActive}" + Maximum="100" + Minimum="0.1" + Value="{Binding CustomResolutionScale}" /> + </StackPanel> + <StackPanel + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale GraphicsAATooltip}" + Text="{locale:Locale GraphicsAALabel}" + Width="250" /> + <ComboBox Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale GraphicsAATooltip}" + SelectedIndex="{Binding AntiAliasingEffect}"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="FXAA" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SmaaLow}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SmaaMedium}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SmaaHigh}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SmaaUltra}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + </StackPanel> + <StackPanel + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" + Text="{locale:Locale GraphicsScalingFilterLabel}" + Width="250" /> + <ComboBox Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" + SelectedIndex="{Binding ScalingFilter}"> + <ComboBoxItem> + <TextBlock Text="Bilinear" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="Nearest" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="FSR" /> + </ComboBoxItem> + </ComboBox> + <controls:SliderScroll Value="{Binding ScalingFilterLevel}" + ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}" + MinWidth="150" + Margin="10,-3,0,0" + Height="32" + Padding="0,-5" + IsVisible="{Binding IsScalingFilterActive}" + TickFrequency="1" + IsSnapToTickEnabled="True" + LargeChange="10" + SmallChange="1" + VerticalAlignment="Center" + Minimum="0" + Maximum="100" /> + <TextBlock Margin="5,0" + Width="40" + IsVisible="{Binding IsScalingFilterActive}" + Text="{Binding ScalingFilterLevelText}"/> + </StackPanel> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale AnisotropyTooltip}" + Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}" + Width="250" /> + <ComboBox SelectedIndex="{Binding MaxAnisotropy}" + Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale AnisotropyTooltip}"> + <ComboBoxItem> + <TextBlock + Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock + Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale AspectRatioTooltip}" + Text="{locale:Locale SettingsTabGraphicsAspectRatio}" + Width="250" /> + <ComboBox SelectedIndex="{Binding AspectRatio}" + Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale AspectRatioTooltip}"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + </StackPanel> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}" + Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}" + Width="250" /> + <ComboBox Width="350" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale GalThreadingTooltip}" + SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale CommonAuto}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale CommonOff}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale CommonOn}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + </StackPanel> + <Separator Height="1" /> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" /> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <StackPanel Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" + Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}" + Width="250" /> + <TextBox Text="{Binding ShaderDumpPath}" + Width="350" + ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" /> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs new file mode 100644 index 00000000..67341330 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsGraphicsView : UserControl + { + public SettingsGraphicsView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml new file mode 100644 index 00000000..b4eae01e --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -0,0 +1,103 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHotkeysView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel" + Focusable="True"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <UserControl.Resources> + <helpers:KeyValueConverter x:Key="Key" /> + </UserControl.Resources> + <ScrollViewer + Name="HotkeysPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel Margin="10" Orientation="Vertical" Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" /> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.ShowUI, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" /> + <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> + <TextBlock + Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}" + TextAlignment="Center" /> + </ToggleButton> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs new file mode 100644 index 00000000..b006d703 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -0,0 +1,81 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsHotkeysView : UserControl + { + private ButtonKeyAssigner _currentAssigner; + private readonly IGamepadDriver _avaloniaKeyboardDriver; + + public SettingsHotkeysView() + { + InitializeComponent(); + _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private void Button_Checked(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + if (_currentAssigner == null && button.IsChecked != null && (bool)button.IsChecked) + { + _currentAssigner = new ButtonKeyAssigner(button); + + this.Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad(_avaloniaKeyboardDriver.GamepadsIds[0]); + IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); + + _currentAssigner.GetInputAndAssign(assigner); + } + else + { + if (_currentAssigner != null) + { + ToggleButton oldButton = _currentAssigner.ToggledButton; + + _currentAssigner.Cancel(); + _currentAssigner = null; + + button.IsChecked = false; + } + } + } + } + + private void Button_Unchecked(object sender, RoutedEventArgs e) + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + + public void Dispose() + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml new file mode 100644 index 00000000..81f4b68b --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -0,0 +1,67 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="InputPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <Panel + Margin="10"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <views:ControllerInputView + Grid.Row="0" + Name="ControllerSettings" /> + <StackPanel + Orientation="Vertical" + Grid.Row="2"> + <Separator + Margin="0 10" + Height="1" /> + <StackPanel + Orientation="Horizontal" + Spacing="10"> + <CheckBox + ToolTip.Tip="{locale:Locale DockModeToggleTooltip}" + MinWidth="0" + IsChecked="{Binding EnableDockedMode}"> + <TextBlock + Text="{locale:Locale SettingsTabInputEnableDockedMode}" /> + </CheckBox> + <CheckBox + ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}" + IsChecked="{Binding EnableKeyboard}"> + <TextBlock + Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" /> + </CheckBox> + <CheckBox + ToolTip.Tip="{locale:Locale DirectMouseTooltip}" + IsChecked="{Binding EnableMouse}"> + <TextBlock + Text="{locale:Locale SettingsTabInputDirectMouseAccess}" /> + </CheckBox> + </StackPanel> + </StackPanel> + </Grid> + </Panel> + </Border> + </ScrollViewer> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs new file mode 100644 index 00000000..e75c9f0c --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsInputView : UserControl + { + public SettingsInputView() + { + InitializeComponent(); + } + + public void Dispose() + { + ControllerSettings.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml new file mode 100644 index 00000000..0fc9ea1b --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml @@ -0,0 +1,120 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsLoggingView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="LoggingPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" /> + <StackPanel Margin="10,0,0,0" Orientation="Vertical"> + <CheckBox IsChecked="{Binding EnableFileLog}" + ToolTip.Tip="{locale:Locale FileLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableStub}" + ToolTip.Tip="{locale:Locale StubLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableInfo}" + ToolTip.Tip="{locale:Locale InfoLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableWarn}" + ToolTip.Tip="{locale:Locale WarnLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableError}" + ToolTip.Tip="{locale:Locale ErrorLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableGuest}" + ToolTip.Tip="{locale:Locale GuestLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" /> + </CheckBox> + </StackPanel> + <Separator Height="1" /> + <StackPanel Orientation="Vertical" Spacing="2"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" /> + <TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" /> + </StackPanel> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <StackPanel Orientation="Vertical"> + <CheckBox IsChecked="{Binding EnableTrace}" + ToolTip.Tip="{locale:Locale TraceLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableFsAccessLog}" + ToolTip.Tip="{locale:Locale FileAccessLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableDebug}" + ToolTip.Tip="{locale:Locale DebugLogTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" /> + </CheckBox> + <StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}" + Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}" + Width="285" /> + <ui:NumberBox + Maximum="3" + Minimum="0" + Width="150" + SpinButtonPlacementMode="Inline" + SmallChange="1" + LargeChange="1" + Value="{Binding FsGlobalAccessLogMode}" /> + </StackPanel> + <StackPanel Margin="0,10,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}" + ToolTip.Tip="{locale:Locale OpenGlLogLevel}" + Width="285" /> + <ComboBox SelectedIndex="{Binding OpenglDebugLevel}" + Width="150" + HorizontalContentAlignment="Left" + ToolTip.Tip="{locale:Locale OpenGlLogLevel}"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock + Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + </StackPanel> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs new file mode 100644 index 00000000..c8df46b3 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsLoggingView : UserControl + { + public SettingsLoggingView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml new file mode 100644 index 00000000..9bb81463 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml @@ -0,0 +1,58 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="NetworkPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkMultiplayer}" /> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale MultiplayerMode}" + ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}" + Width="200" /> + <ComboBox SelectedIndex="{Binding MultiplayerModeIndex}" + ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}" + HorizontalContentAlignment="Left" + ItemsSource="{Binding MultiplayerModes}" + Width="250" /> + </StackPanel> + <Separator Height="1" /> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" /> + <CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}"> + <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}" + ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> + </CheckBox> + <StackPanel Margin="10,0,0,0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabNetworkInterface}" + ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" + Width="200" /> + <ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}" + ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" + HorizontalContentAlignment="Left" + ItemsSource="{Binding NetworkInterfaceList}" + Width="250" /> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs new file mode 100644 index 00000000..b771933e --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsNetworkView : UserControl + { + public SettingsNetworkView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml new file mode 100644 index 00000000..e6f7c6e4 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml @@ -0,0 +1,224 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsSystemView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <UserControl.Resources> + <helpers:TimeZoneConverter x:Key="TimeZone" /> + </UserControl.Resources> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="SystemPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock + Classes="h1" + Text="{locale:Locale SettingsTabSystemCore}" /> + <StackPanel + Margin="10,0,0,0" + Orientation="Vertical"> + <StackPanel + Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemSystemRegion}" + Width="250" /> + <ComboBox + SelectedIndex="{Binding Region}" + ToolTip.Tip="{locale:Locale RegionTooltip}" + HorizontalContentAlignment="Left" + Width="350"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel + Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemSystemLanguage}" + ToolTip.Tip="{locale:Locale LanguageTooltip}" + Width="250" /> + <ComboBox + SelectedIndex="{Binding Language}" + ToolTip.Tip="{locale:Locale LanguageTooltip}" + HorizontalContentAlignment="Left" + Width="350"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel + Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemSystemTimeZone}" + ToolTip.Tip="{locale:Locale TimezoneTooltip}" + Width="250" /> + <AutoCompleteBox + Name="TimeZoneBox" + Width="350" + MaxDropDownHeight="500" + FilterMode="Contains" + ItemsSource="{Binding TimeZones}" + SelectionChanged="TimeZoneBox_OnSelectionChanged" + Text="{Binding Path=TimeZone, Mode=OneWay}" + TextChanged="TimeZoneBox_OnTextChanged" + ToolTip.Tip="{locale:Locale TimezoneTooltip}" + ValueMemberBinding="{Binding Mode=OneWay, Converter={StaticResource TimeZone}}" /> + </StackPanel> + <StackPanel + Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="{locale:Locale SettingsTabSystemSystemTime}" + ToolTip.Tip="{locale:Locale TimeTooltip}" + Width="250"/> + <DatePicker + VerticalAlignment="Center" + SelectedDate="{Binding CurrentDate}" + ToolTip.Tip="{locale:Locale TimeTooltip}" + Width="350" /> + </StackPanel> + <StackPanel + Margin="250,0,0,10" + Orientation="Horizontal"> + <TimePicker + VerticalAlignment="Center" + ClockIdentifier="24HourClock" + SelectedTime="{Binding CurrentTime}" + Width="350" + ToolTip.Tip="{locale:Locale TimeTooltip}" /> + </StackPanel> + <CheckBox IsChecked="{Binding EnableVsync}"> + <TextBlock + Text="{locale:Locale SettingsTabSystemEnableVsync}" + ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" /> + </CheckBox> + <CheckBox IsChecked="{Binding EnableFsIntegrityChecks}"> + <TextBlock + Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}" + ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" /> + </CheckBox> + </StackPanel> + <Separator Height="1" /> + <StackPanel + Orientation="Vertical" + Spacing="2"> + <TextBlock + Classes="h1" + Text="{locale:Locale SettingsTabSystemHacks}" /> + <TextBlock + Foreground="{DynamicResource SecondaryTextColor}" + Text="{locale:Locale SettingsTabSystemHacksNote}" /> + </StackPanel> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical"> + <CheckBox + IsChecked="{Binding ExpandDramSize}" + ToolTip.Tip="{locale:Locale DRamTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" /> + </CheckBox> + <CheckBox + IsChecked="{Binding IgnoreMissingServices}" + ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}"> + <TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" /> + </CheckBox> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs new file mode 100644 index 00000000..2c9eac28 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia.Controls; +using Ryujinx.Ava.UI.ViewModels; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsSystemView : UserControl + { + public SettingsViewModel ViewModel; + + public SettingsSystemView() + { + InitializeComponent(); + } + + private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (e.AddedItems != null && e.AddedItems.Count > 0) + { + if (e.AddedItems[0] is TimeZone timeZone) + { + e.Handled = true; + + ViewModel.ValidateAndSetTimeZone(timeZone.Location); + } + } + } + + private void TimeZoneBox_OnTextChanged(object sender, TextChangedEventArgs e) + { + if (sender is AutoCompleteBox box && box.SelectedItem is TimeZone timeZone) + { + ViewModel.ValidateAndSetTimeZone(timeZone.Location); + } + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml new file mode 100644 index 00000000..6504637e --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -0,0 +1,128 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUiView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + x:DataType="viewModels:SettingsViewModel"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <ScrollViewer + Name="UiPage" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto"> + <Border Classes="settings"> + <StackPanel + Margin="10" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" /> + <StackPanel Margin="10,0,0,0" Orientation="Vertical"> + <CheckBox IsChecked="{Binding EnableDiscordIntegration}"> + <TextBlock VerticalAlignment="Center" + ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}" + Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> + </CheckBox> + <CheckBox IsChecked="{Binding CheckUpdatesOnStart}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" /> + </CheckBox> + <CheckBox IsChecked="{Binding ShowConfirmExit}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" /> + </CheckBox> + <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> + <TextBlock VerticalAlignment="Center" + Text="{locale:Locale SettingsTabGeneralHideCursor}" + Width="150" /> + <ComboBox SelectedIndex="{Binding HideCursor}" + HorizontalContentAlignment="Left" + MinWidth="100"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorNever}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorAlways}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + <StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="{locale:Locale SettingsTabGeneralTheme}" + Width="150" /> + <ComboBox SelectedIndex="{Binding BaseStyleIndex}" + HorizontalContentAlignment="Left" + MinWidth="100"> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGeneralThemeLight}" /> + </ComboBoxItem> + <ComboBoxItem> + <TextBlock Text="{locale:Locale SettingsTabGeneralThemeDark}" /> + </ComboBoxItem> + </ComboBox> + </StackPanel> + </StackPanel> + <Separator Height="1" /> + <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" /> + <StackPanel + Margin="10,0,0,0" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <ListBox + Name="GameList" + MinHeight="230" + ItemsSource="{Binding GameDirectories}"> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Padding" Value="10" /> + <Setter Property="Background" Value="{DynamicResource ListBoxBackground}" /> + </Style> + </ListBox.Styles> + </ListBox> + <Grid HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <TextBox + Name="PathBox" + Margin="0" + ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}" + VerticalAlignment="Stretch" /> + <Button + Name="AddButton" + Grid.Column="1" + MinWidth="90" + Margin="10,0,0,0" + ToolTip.Tip="{locale:Locale AddGameDirTooltip}" + Click="AddButton_OnClick"> + <TextBlock HorizontalAlignment="Center" + Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveButton" + Grid.Column="2" + MinWidth="90" + Margin="10,0,0,0" + ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}" + Click="RemoveButton_OnClick"> + <TextBlock HorizontalAlignment="Center" + Text="{locale:Locale SettingsTabGeneralRemove}" /> + </Button> + </Grid> + </StackPanel> + </StackPanel> + </Border> + </ScrollViewer> +</UserControl> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs new file mode 100644 index 00000000..996d15cd --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs @@ -0,0 +1,65 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Avalonia.VisualTree; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsUiView : UserControl + { + public SettingsViewModel ViewModel; + + public SettingsUiView() + { + InitializeComponent(); + } + + private async void AddButton_OnClick(object sender, RoutedEventArgs e) + { + string path = PathBox.Text; + + if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path)) + { + ViewModel.GameDirectories.Add(path); + ViewModel.DirectoryChanged = true; + } + else + { + if (this.GetVisualRoot() is Window window) + { + var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + AllowMultiple = false, + }); + + if (result.Count > 0) + { + ViewModel.GameDirectories.Add(result[0].Path.LocalPath); + ViewModel.DirectoryChanged = true; + } + } + } + } + + private void RemoveButton_OnClick(object sender, RoutedEventArgs e) + { + int oldIndex = GameList.SelectedIndex; + + foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>())) + { + ViewModel.GameDirectories.Remove(path); + ViewModel.DirectoryChanged = true; + } + + if (GameList.ItemCount > 0) + { + GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0; + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserEditorView.axaml b/src/Ryujinx/UI/Views/User/UserEditorView.axaml new file mode 100644 index 00000000..ab83c2cd --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserEditorView.axaml @@ -0,0 +1,122 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.User.UserEditorView" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + Margin="0" + MinWidth="500" + Padding="0" + mc:Ignorable="d" + Focusable="True" + x:DataType="models:TempProfile"> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + </UserControl.Resources> + <Grid Margin="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <StackPanel + Grid.Row="0" + Grid.Column="0" + HorizontalAlignment="Stretch" + Orientation="Vertical" + Spacing="10"> + <TextBlock Text="{locale:Locale UserProfilesName}" /> + <TextBox + Name="NameBox" + Width="300" + HorizontalAlignment="Stretch" + MaxLength="{Binding MaxProfileNameLength}" + Watermark="{locale:Locale ProfileNameSelectionWatermark}" + Text="{Binding Name}" /> + <TextBlock Name="IdText" Text="{locale:Locale UserProfilesUserId}" /> + <TextBox + Name="IdLabel" + Width="300" + HorizontalAlignment="Stretch" + IsReadOnly="True" + Text="{Binding UserIdString}" /> + </StackPanel> + <StackPanel + Grid.Row="0" + Grid.Column="1" + HorizontalAlignment="Right" + VerticalAlignment="Stretch" + Orientation="Vertical"> + <Border + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1"> + <Panel> + <ui:SymbolIcon + FontSize="60" + Width="96" + Height="96" + Margin="0" + Foreground="{DynamicResource AppListHoverBackgroundColor}" + HorizontalAlignment="Stretch" + VerticalAlignment="Top" + Symbol="Camera" /> + <Image + Name="ProfileImage" + Width="96" + Height="96" + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Top" + Source="{Binding Image, Converter={StaticResource ByteImage}}" /> + </Panel> + </Border> + </StackPanel> + <StackPanel + Grid.Row="1" + Grid.Column="0" + Grid.ColumnSpan="2" + HorizontalAlignment="Left" + Orientation="Horizontal" + Margin="0 24 0 0" + Spacing="10"> + <Button + Width="50" + MinWidth="50" + Click="BackButton_Click"> + <ui:SymbolIcon Symbol="Back" /> + </Button> + </StackPanel> + <StackPanel + Grid.Row="1" + Grid.Column="0" + Grid.ColumnSpan="2" + HorizontalAlignment="Right" + Orientation="Horizontal" + Margin="0 24 0 0" + Spacing="10"> + <Button + Name="DeleteButton" + Click="DeleteButton_Click" + Content="{locale:Locale UserProfilesDelete}" /> + <Button + Name="ChangePictureButton" + Click="ChangePictureButton_Click" + Content="{locale:Locale UserProfilesChangeProfileImage}" /> + <Button + Name="AddPictureButton" + Click="ChangePictureButton_Click" + Content="{locale:Locale UserProfilesSetProfileImage}" /> + <Button + Name="SaveButton" + Click="SaveButton_Click" + Content="{locale:Locale Save}" /> + </StackPanel> + </Grid> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs new file mode 100644 index 00000000..588fa471 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs @@ -0,0 +1,165 @@ +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserEditorView : UserControl + { + private NavigationDialogHost _parent; + private UserProfile _profile; + private bool _isNewUser; + + public TempProfile TempProfile { get; set; } + public static uint MaxProfileNameLength => 0x20; + public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId; + + public UserEditorView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + var (parent, profile, isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter; + _isNewUser = isNewUser; + _profile = profile; + TempProfile = new TempProfile(_profile); + + _parent = parent; + break; + } + + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " + + $"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}"; + + DataContext = TempProfile; + + AddPictureButton.IsVisible = _isNewUser; + ChangePictureButton.IsVisible = !_isNewUser; + IdLabel.IsVisible = _profile != null; + IdText.IsVisible = _profile != null; + if (!_isNewUser && IsDeletable) + { + DeleteButton.IsVisible = true; + } + else + { + DeleteButton.IsVisible = false; + } + } + } + + private async void BackButton_Click(object sender, RoutedEventArgs e) + { + if (_isNewUser) + { + if (TempProfile.Name != String.Empty || TempProfile.Image != null) + { + if (await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage], + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage])) + { + _parent?.GoBack(); + } + } + else + { + _parent?.GoBack(); + } + } + else + { + if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image) + { + if (await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage], + LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage])) + { + _parent?.GoBack(); + } + } + else + { + _parent?.GoBack(); + } + } + } + + private void DeleteButton_Click(object sender, RoutedEventArgs e) + { + _parent.DeleteUser(_profile); + } + + private void SaveButton_Click(object sender, RoutedEventArgs e) + { + DataValidationErrors.ClearErrors(NameBox); + + if (string.IsNullOrWhiteSpace(TempProfile.Name)) + { + DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError])); + + return; + } + + if (TempProfile.Image == null) + { + _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); + + return; + } + + if (_profile != null && !_isNewUser) + { + _profile.Name = TempProfile.Name; + _profile.Image = TempProfile.Image; + _profile.UpdateState(); + _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name); + _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image); + } + else if (_isNewUser) + { + _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId); + } + else + { + return; + } + + _parent?.GoBack(); + } + + public void SelectProfileImage() + { + _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); + } + + private void ChangePictureButton_Click(object sender, RoutedEventArgs e) + { + if (_profile != null || _isNewUser) + { + SelectProfileImage(); + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml new file mode 100644 index 00000000..21dfc909 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml @@ -0,0 +1,113 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + Width="528" + d:DesignWidth="578" + d:DesignHeight="350" + x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel" + Focusable="True"> + <Design.DataContext> + <viewModels:UserFirmwareAvatarSelectorViewModel /> + </Design.DataContext> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + </UserControl.Resources> + <Grid + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <ListBox + Grid.Row="1" + BorderThickness="0" + SelectedIndex="{Binding SelectedIndex}" + Height="400" + ItemsSource="{Binding Images}" + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <WrapPanel + Orientation="Horizontal" + Margin="0" + HorizontalAlignment="Center" /> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="CornerRadius" Value="4" /> + <Setter Property="Width" Value="85" /> + <Setter Property="MaxWidth" Value="85" /> + <Setter Property="MinWidth" Value="85" /> + </Style> + <Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator"> + <Setter Property="MinHeight" Value="70" /> + </Style> + </ListBox.Styles> + <ListBox.ItemTemplate> + <DataTemplate> + <Panel + Background="{Binding BackgroundColor}" + Margin="5"> + <Image Source="{Binding Data, Converter={StaticResource ByteImage}}" /> + </Panel> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + <StackPanel + Grid.Row="3" + Orientation="Horizontal" + Spacing="10" + Margin="0 24 0 0" + HorizontalAlignment="Left"> + <Button + Width="50" + MinWidth="50" + Height="35" + Click="GoBack"> + <ui:SymbolIcon Symbol="Back" /> + </Button> + </StackPanel> + <StackPanel + Grid.Row="3" + Orientation="Horizontal" + Spacing="10" + Margin="0 24 0 0" + HorizontalAlignment="Right"> + <ui:ColorPickerButton + FlyoutPlacement="Top" + IsMoreButtonVisible="False" + UseColorPalette="False" + UseColorTriangle="False" + UseColorWheel="False" + ShowAcceptDismissButtons="False" + IsAlphaEnabled="False" + Color="{Binding BackgroundColor, Mode=TwoWay}" + Name="ColorButton"> + <ui:ColorPickerButton.Styles> + <Style Selector="Grid#Root > DockPanel > Grid"> + <Setter Property="IsVisible" Value="False" /> + </Style> + </ui:ColorPickerButton.Styles> + </ui:ColorPickerButton> + <Button + Content="{locale:Locale AvatarChoose}" + Height="35" + Name="ChooseButton" + Click="ChooseButton_OnClick" /> + </StackPanel> + </Grid> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs new file mode 100644 index 00000000..b6376866 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs @@ -0,0 +1,89 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.IO; +using Image = SixLabors.ImageSharp.Image; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserFirmwareAvatarSelectorView : UserControl + { + private NavigationDialogHost _parent; + private TempProfile _profile; + + public UserFirmwareAvatarSelectorView(ContentManager contentManager) + { + ContentManager = contentManager; + + DataContext = ViewModel; + + InitializeComponent(); + } + + public UserFirmwareAvatarSelectorView() + { + InitializeComponent(); + + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + if (arg.NavigationMode == NavigationMode.New) + { + (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; + ContentManager = _parent.ContentManager; + if (Program.PreviewerDetached) + { + ViewModel = new UserFirmwareAvatarSelectorViewModel(); + } + + DataContext = ViewModel; + } + } + } + + public ContentManager ContentManager { get; private set; } + + internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent.GoBack(); + } + + private void ChooseButton_OnClick(object sender, RoutedEventArgs e) + { + if (ViewModel.SelectedImage != null) + { + MemoryStream streamJpg = new(); + Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder()); + + avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( + ViewModel.BackgroundColor.R, + ViewModel.BackgroundColor.G, + ViewModel.BackgroundColor.B, + ViewModel.BackgroundColor.A))); + avatarImage.SaveAsJpeg(streamJpg); + + _profile.Image = streamJpg.ToArray(); + + _parent.GoBack(); + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml new file mode 100644 index 00000000..b1786430 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml @@ -0,0 +1,62 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels" + Focusable="True" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView" + x:DataType="viewModles:UserProfileImageSelectorViewModel" + Width="500" + d:DesignWidth="500"> + <Design.DataContext> + <viewModles:UserProfileImageSelectorViewModel /> + </Design.DataContext> + <Grid + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="70" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + Grid.Row="0" + TextWrapping="Wrap" + HorizontalAlignment="Left" + TextAlignment="Start" + Text="{locale:Locale ProfileImageSelectionNote}" /> + <StackPanel + Grid.Row="2" + Spacing="10" + HorizontalAlignment="Left" + Orientation="Horizontal"> + <Button + Width="50" + MinWidth="50" + Click="GoBack"> + <ui:SymbolIcon Symbol="Back" /> + </Button> + </StackPanel> + <StackPanel + Grid.Row="2" + Spacing="10" + HorizontalAlignment="Right" + Orientation="Horizontal"> + <Button + Name="Import" + Click="Import_OnClick"> + <TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" /> + </Button> + <Button + Name="SelectFirmwareImage" + IsEnabled="{Binding FirmwareFound}" + Click="SelectFirmwareImage_OnClick"> + <TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" /> + </Button> + </StackPanel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs new file mode 100644 index 00000000..fabfaa4e --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs @@ -0,0 +1,116 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using System.Collections.Generic; +using System.IO; +using Image = SixLabors.ImageSharp.Image; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserProfileImageSelectorView : UserControl + { + private ContentManager _contentManager; + private NavigationDialogHost _parent; + private TempProfile _profile; + + internal UserProfileImageSelectorViewModel ViewModel { get; private set; } + + public UserProfileImageSelectorView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; + _contentManager = _parent.ContentManager; + + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}"; + + if (Program.PreviewerDetached) + { + DataContext = ViewModel = new UserProfileImageSelectorViewModel(); + ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null; + } + + break; + case NavigationMode.Back: + if (_profile.Image != null) + { + _parent.GoBack(); + } + break; + } + } + } + + private async void Import_OnClick(object sender, RoutedEventArgs e) + { + var window = this.GetVisualRoot() as Window; + var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = new List<FilePickerFileType> + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" }, + AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" }, + MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }, + }, + }, + }); + + if (result.Count > 0) + { + _profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath)); + _parent.GoBack(); + } + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent.GoBack(); + } + + private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) + { + if (ViewModel.FirmwareFound) + { + _parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile)); + } + } + + private static byte[] ProcessProfileImage(byte[] buffer) + { + using Image image = Image.Load(buffer); + + image.Mutate(x => x.Resize(256, 256)); + + using MemoryStream streamJpg = new(); + + image.SaveAsJpeg(streamJpg); + + return streamJpg.ToArray(); + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml new file mode 100644 index 00000000..3fdb4ab9 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml @@ -0,0 +1,82 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + d:DesignWidth="550" + d:DesignHeight="450" + Width="500" + Height="400" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView" + x:DataType="viewModels:UserProfileViewModel" + Focusable="True"> + <Design.DataContext> + <viewModels:UserProfileViewModel /> + </Design.DataContext> + <Grid HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + <Border + CornerRadius="5" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1" + Grid.Row="0"> + <Panel> + <ListBox + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ItemsSource="{Binding LostProfiles}"> + <ListBox.ItemTemplate> + <DataTemplate> + <Border + Margin="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ClipToBounds="True" + CornerRadius="5"> + <Grid Margin="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <TextBlock + HorizontalAlignment="Stretch" + Text="{Binding UserId}" + TextAlignment="Start" + TextWrapping="Wrap" /> + <Button Grid.Column="1" + HorizontalAlignment="Right" + Click="Recover" + CommandParameter="{Binding}" + Content="{locale:Locale Recover}"/> + </Grid> + </Border> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + <TextBlock + IsVisible="{Binding IsEmpty}" + TextAlignment="Center" + Text="{locale:Locale UserProfilesRecoverEmptyList}"/> + </Panel> + </Border> + <StackPanel + Grid.Row="1" + Margin="0 24 0 0" + Orientation="Horizontal"> + <Button + Width="50" + MinWidth="50" + Click="GoBack"> + <ui:SymbolIcon Symbol="Back"/> + </Button> + </StackPanel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs new file mode 100644 index 00000000..31934349 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserRecovererView : UserControl + { + private NavigationDialogHost _parent; + + public UserRecovererView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + var parent = (NavigationDialogHost)arg.Parameter; + + _parent = parent; + + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}"; + + break; + } + } + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent?.GoBack(); + } + + private void Recover(object sender, RoutedEventArgs e) + { + _parent?.RecoverLostAccounts(); + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml new file mode 100644 index 00000000..8bc5125a --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml @@ -0,0 +1,213 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + mc:Ignorable="d" + d:DesignWidth="600" + d:DesignHeight="500" + Height="450" + Width="550" + x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView" + x:DataType="viewModels:UserSaveManagerViewModel" + Focusable="True"> + <Design.DataContext> + <viewModels:UserSaveManagerViewModel /> + </Design.DataContext> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + </UserControl.Resources> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid + Grid.Row="0" + HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <StackPanel + Spacing="10" + Orientation="Horizontal" + HorizontalAlignment="Left" + VerticalAlignment="Center"> + <Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" /> + <ComboBox SelectedIndex="{Binding SortIndex}" Width="100"> + <ComboBoxItem> + <Label + VerticalAlignment="Center" + HorizontalContentAlignment="Left" + Content="{locale:Locale Name}" /> + </ComboBoxItem> + <ComboBoxItem> + <Label + VerticalAlignment="Center" + HorizontalContentAlignment="Left" + Content="{locale:Locale Size}" /> + </ComboBoxItem> + <ComboBox.Styles> + <Style Selector="ContentControl#ContentPresenter"> + <Setter Property="HorizontalAlignment" Value="Left" /> + </Style> + </ComboBox.Styles> + </ComboBox> + <ComboBox SelectedIndex="{Binding OrderIndex}" Width="150"> + <ComboBoxItem> + <Label + VerticalAlignment="Center" + HorizontalContentAlignment="Left" + Content="{locale:Locale OrderAscending}" /> + </ComboBoxItem> + <ComboBoxItem> + <Label + VerticalAlignment="Center" + HorizontalContentAlignment="Left" + Content="{locale:Locale OrderDescending}" /> + </ComboBoxItem> + <ComboBox.Styles> + <Style Selector="ContentControl#ContentPresenter"> + <Setter Property="HorizontalAlignment" Value="Left" /> + </Style> + </ComboBox.Styles> + </ComboBox> + </StackPanel> + <Grid + Grid.Column="1" + HorizontalAlignment="Stretch" + Margin="10,0, 0, 0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition/> + </Grid.ColumnDefinitions> + <Label Content="{locale:Locale Search}" VerticalAlignment="Center" /> + <TextBox + Margin="5,0,0,0" + Grid.Column="1" + HorizontalAlignment="Stretch" + Text="{Binding Search}" /> + </Grid> + </Grid> + <Border + Grid.Row="1" + Margin="0,5" + BorderThickness="1" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + CornerRadius="5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <ListBox + Name="SaveList" + ItemsSource="{Binding Views}" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Padding" Value="10" /> + <Setter Property="Margin" Value="5" /> + <Setter Property="CornerRadius" Value="4" /> + </Style> + <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> + <Setter Property="IsVisible" Value="False" /> + </Style> + </ListBox.Styles> + <ListBox.ItemTemplate> + <DataTemplate x:DataType="models:SaveModel"> + <Grid HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <StackPanel + Grid.Column="0" + Orientation="Horizontal" + Spacing="5"> + <Border + Height="42" + Width="42" + Padding="10" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1" + IsVisible="{Binding !InGameList}"> + <ui:SymbolIcon + Symbol="Help" + FontSize="30" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Border> + <Image + IsVisible="{Binding InGameList}" + Width="42" + Height="42" + Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> + <TextBlock + MaxLines="3" + Width="320" + Margin="5" + TextWrapping="Wrap" + Text="{Binding Title}" + VerticalAlignment="Center" /> + </StackPanel> + <StackPanel + Grid.Column="1" + Spacing="10" + HorizontalAlignment="Right" + Orientation="Horizontal"> + <Label + Content="{Binding SizeString}" + IsVisible="{Binding SizeAvailable}" + VerticalAlignment="Center" + HorizontalAlignment="Right" /> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Name="OpenLocation" + Click="OpenLocation"> + <ui:SymbolIcon + Symbol="OpenFolder" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Name="Delete" + Click="Delete"> + <ui:SymbolIcon + Symbol="Delete" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + </StackPanel> + </Grid> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Border> + <StackPanel + Grid.Row="2" + Margin="0 24 0 0" + Orientation="Horizontal"> + <Button + Width="50" + MinWidth="50" + Click="GoBack"> + <ui:SymbolIcon Symbol="Back" /> + </Button> + </StackPanel> + </Grid> +</UserControl> \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs new file mode 100644 index 00000000..00a229fa --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs @@ -0,0 +1,148 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; +using UserId = LibHac.Fs.UserId; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserSaveManagerView : UserControl + { + internal UserSaveManagerViewModel ViewModel { get; private set; } + + private AccountManager _accountManager; + private HorizonClient _horizonClient; + private VirtualFileSystem _virtualFileSystem; + private NavigationDialogHost _parent; + + public UserSaveManagerView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + var (parent, accountManager, client, virtualFileSystem) = ((NavigationDialogHost parent, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter; + _accountManager = accountManager; + _horizonClient = client; + _virtualFileSystem = virtualFileSystem; + + _parent = parent; + break; + } + + DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager); + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}"; + + Task.Run(LoadSaves); + } + } + + public void LoadSaves() + { + ViewModel.Saves.Clear(); + var saves = new ObservableCollection<SaveModel>(); + var saveDataFilter = SaveDataFilter.Make( + programId: default, + saveType: SaveDataType.Account, + new UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low), + saveDataId: default, + index: default); + + using var saveDataIterator = new UniqueRef<SaveDataIterator>(); + + _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10]; + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + var save = saveDataInfo[i]; + if (save.ProgramId.Value != 0) + { + var saveModel = new SaveModel(save); + saves.Add(saveModel); + } + } + } + + Dispatcher.UIThread.Post(() => + { + ViewModel.Saves = saves; + ViewModel.Sort(); + }); + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent?.GoBack(); + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is SaveModel saveModel) + { + ApplicationHelper.OpenSaveDir(saveModel.SaveId); + } + } + } + + private async void Delete(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is SaveModel saveModel) + { + var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave], + LocaleManager.Instance[LocaleKeys.IrreversibleActionNote], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], ""); + + if (result == UserResult.Yes) + { + _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId); + ViewModel.Saves.Remove(saveModel); + ViewModel.Sort(); + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml new file mode 100644 index 00000000..3a9de303 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml @@ -0,0 +1,162 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Views.User.UserSelectorViews" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + d:DesignHeight="450" + MinWidth="500" + d:DesignWidth="800" + mc:Ignorable="d" + Focusable="True" + x:DataType="viewModels:UserProfileViewModel"> + <UserControl.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + </UserControl.Resources> + <Design.DataContext> + <viewModels:UserProfileViewModel /> + </Design.DataContext> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Border + CornerRadius="5" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1"> + <ListBox + MaxHeight="300" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + SelectionChanged="ProfilesList_SelectionChanged" + Background="Transparent" + ItemsSource="{Binding Profiles}"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <WrapPanel + HorizontalAlignment="Left" + VerticalAlignment="Center" + Orientation="Horizontal"/> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Margin" Value="5 5 0 5" /> + <Setter Property="CornerRadius" Value="5" /> + </Style> + <Style Selector="Rectangle#SelectionIndicator"> + <Setter Property="Opacity" Value="0" /> + </Style> + </ListBox.Styles> + <ListBox.DataTemplates> + <DataTemplate + DataType="models:UserProfile"> + <Grid + PointerEntered="Grid_PointerEntered" + PointerExited="Grid_OnPointerExited"> + <Border + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ClipToBounds="True" + CornerRadius="5" + Background="{Binding BackgroundColor}"> + <StackPanel + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Image + Width="96" + Height="96" + HorizontalAlignment="Stretch" + VerticalAlignment="Top" + Source="{Binding Image, Converter={StaticResource ByteImage}}" /> + <TextBlock + HorizontalAlignment="Stretch" + MaxWidth="90" + Text="{Binding Name}" + TextAlignment="Center" + TextWrapping="Wrap" + TextTrimming="CharacterEllipsis" + MaxLines="2" + Margin="5" /> + </StackPanel> + </Border> + <Border + Margin="2" + Height="24" + Width="24" + CornerRadius="12" + HorizontalAlignment="Right" + VerticalAlignment="Top" + Background="{DynamicResource ThemeContentBackgroundColor}" + IsVisible="{Binding IsPointerOver}"> + <Button + MaxHeight="24" + MaxWidth="24" + MinHeight="24" + MinWidth="24" + CornerRadius="12" + Padding="0" + Click="EditUser"> + <ui:SymbolIcon Symbol="Edit" /> + </Button> + </Border> + </Grid> + </DataTemplate> + <DataTemplate + DataType="viewModels:BaseModel"> + <Panel + Height="118" + Width="96"> + <Button + MinWidth="50" + MinHeight="50" + MaxWidth="50" + MaxHeight="50" + CornerRadius="25" + Margin="10" + Padding="0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Click="AddUser"> + <ui:SymbolIcon Symbol="Add" /> + </Button> + <Panel.Styles> + <Style Selector="Panel"> + <Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/> + </Style> + </Panel.Styles> + </Panel> + </DataTemplate> + </ListBox.DataTemplates> + </ListBox> + </Border> + <StackPanel + Grid.Row="1" + Margin="0 24 0 0" + HorizontalAlignment="Left" + Orientation="Horizontal" + Spacing="10"> + <Button + Click="ManageSaves" + Content="{locale:Locale UserProfilesManageSaves}" /> + <Button + Click="RecoverLostAccounts" + Content="{locale:Locale UserProfilesRecoverLostAccounts}" /> + </StackPanel> + <StackPanel + Grid.Row="1" + Margin="0 24 0 0" + HorizontalAlignment="Right" + Orientation="Horizontal"> + <Button + Click="Close" + Content="{locale:Locale UserProfilesClose}" /> + </StackPanel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs new file mode 100644 index 00000000..fa3383aa --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs @@ -0,0 +1,129 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserSelectorViews : UserControl + { + private NavigationDialogHost _parent; + + public UserProfileViewModel ViewModel { get; set; } + + public UserSelectorViews() + { + InitializeComponent(); + + if (Program.PreviewerDetached) + { + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + if (arg.NavigationMode == NavigationMode.New) + { + _parent = (NavigationDialogHost)arg.Parameter; + ViewModel = _parent.ViewModel; + } + + if (arg.NavigationMode == NavigationMode.Back) + { + ((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]; + } + + DataContext = ViewModel; + } + } + + private void Grid_PointerEntered(object sender, PointerEventArgs e) + { + if (sender is Grid grid) + { + if (grid.DataContext is UserProfile profile) + { + profile.IsPointerOver = true; + } + } + } + + private void Grid_OnPointerExited(object sender, PointerEventArgs e) + { + if (sender is Grid grid) + { + if (grid.DataContext is UserProfile profile) + { + profile.IsPointerOver = false; + } + } + } + + private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is ListBox listBox) + { + int selectedIndex = listBox.SelectedIndex; + + if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count) + { + if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile) + { + _parent?.AccountManager?.OpenUser(userProfile.UserId); + + foreach (BaseModel profile in ViewModel.Profiles) + { + if (profile is UserProfile uProfile) + { + uProfile.UpdateState(); + } + } + } + } + } + } + + private void AddUser(object sender, RoutedEventArgs e) + { + _parent.AddUser(); + } + + private void EditUser(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is UserProfile userProfile) + { + _parent.EditUser(userProfile); + } + } + } + + private void ManageSaves(object sender, RoutedEventArgs e) + { + _parent.ManageSaves(); + } + + private void RecoverLostAccounts(object sender, RoutedEventArgs e) + { + _parent.RecoverLostAccounts(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)_parent.Parent).Hide(); + } + } +} diff --git a/src/Ryujinx/UI/VulkanRenderer.cs b/src/Ryujinx/UI/VulkanRenderer.cs deleted file mode 100644 index eefc5699..00000000 --- a/src/Ryujinx/UI/VulkanRenderer.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Gdk; -using Ryujinx.Common.Configuration; -using Ryujinx.Input.HLE; -using Ryujinx.UI.Helper; -using SPB.Graphics.Vulkan; -using SPB.Platform.Metal; -using SPB.Platform.Win32; -using SPB.Platform.X11; -using SPB.Windowing; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.UI -{ - public partial class VulkanRenderer : RendererWidgetBase - { - public NativeWindowBase NativeWindow { get; private set; } - private UpdateBoundsCallbackDelegate _updateBoundsCallback; - - public VulkanRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { } - - private NativeWindowBase RetrieveNativeWindow() - { - if (OperatingSystem.IsWindows()) - { - IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); - - return new SimpleWin32Window(new NativeHandle(windowHandle)); - } - else if (OperatingSystem.IsLinux()) - { - IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); - IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); - - return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); - } - else if (OperatingSystem.IsMacOS()) - { - IntPtr metalLayer = MetalHelper.GetMetalLayer(Display, Window, out IntPtr nsView, out _updateBoundsCallback); - - return new SimpleMetalWindow(new NativeHandle(nsView), new NativeHandle(metalLayer)); - } - - throw new NotImplementedException(); - } - - [LibraryImport("libgdk-3-0.dll")] - private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); - - [LibraryImport("libgdk-3.so.0")] - private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); - - [LibraryImport("libgdk-3.so.0")] - private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); - - protected override bool OnConfigureEvent(EventConfigure evnt) - { - if (NativeWindow == null) - { - NativeWindow = RetrieveNativeWindow(); - - WaitEvent.Set(); - } - - bool result = base.OnConfigureEvent(evnt); - - _updateBoundsCallback?.Invoke(Window); - - return result; - } - - public unsafe IntPtr CreateWindowSurface(IntPtr instance) - { - return VulkanHelper.CreateWindowSurface(instance, NativeWindow); - } - - public override void InitializeRenderer() { } - - public override void SwapBuffers() { } - - protected override string GetGpuBackendName() - { - return "Vulkan"; - } - - protected override void Dispose(bool disposing) - { - Device?.DisposeGpu(); - - NpadManager.Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs deleted file mode 100644 index 8ee1cd2f..00000000 --- a/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs +++ /dev/null @@ -1,233 +0,0 @@ -using Gtk; -using System; - -namespace Ryujinx.UI.Widgets -{ - public partial class GameTableContextMenu : Menu - { - private MenuItem _openSaveUserDirMenuItem; - private MenuItem _openSaveDeviceDirMenuItem; - private MenuItem _openSaveBcatDirMenuItem; - private MenuItem _manageTitleUpdatesMenuItem; - private MenuItem _manageDlcMenuItem; - private MenuItem _manageCheatMenuItem; - private MenuItem _openTitleModDirMenuItem; - private MenuItem _openTitleSdModDirMenuItem; - private Menu _extractSubMenu; - private MenuItem _extractMenuItem; - private MenuItem _extractRomFsMenuItem; - private MenuItem _extractExeFsMenuItem; - private MenuItem _extractLogoMenuItem; - private Menu _manageSubMenu; - private MenuItem _manageCacheMenuItem; - private MenuItem _purgePtcCacheMenuItem; - private MenuItem _purgeShaderCacheMenuItem; - private MenuItem _openPtcDirMenuItem; - private MenuItem _openShaderCacheDirMenuItem; - private MenuItem _createShortcutMenuItem; - - private void InitializeComponent() - { - // - // _openSaveUserDirMenuItem - // - _openSaveUserDirMenuItem = new MenuItem("Open User Save Directory") - { - TooltipText = "Open the directory which contains Application's User Saves.", - }; - _openSaveUserDirMenuItem.Activated += OpenSaveUserDir_Clicked; - - // - // _openSaveDeviceDirMenuItem - // - _openSaveDeviceDirMenuItem = new MenuItem("Open Device Save Directory") - { - TooltipText = "Open the directory which contains Application's Device Saves.", - }; - _openSaveDeviceDirMenuItem.Activated += OpenSaveDeviceDir_Clicked; - - // - // _openSaveBcatDirMenuItem - // - _openSaveBcatDirMenuItem = new MenuItem("Open BCAT Save Directory") - { - TooltipText = "Open the directory which contains Application's BCAT Saves.", - }; - _openSaveBcatDirMenuItem.Activated += OpenSaveBcatDir_Clicked; - - // - // _manageTitleUpdatesMenuItem - // - _manageTitleUpdatesMenuItem = new MenuItem("Manage Title Updates") - { - TooltipText = "Open the Title Update management window", - }; - _manageTitleUpdatesMenuItem.Activated += ManageTitleUpdates_Clicked; - - // - // _manageDlcMenuItem - // - _manageDlcMenuItem = new MenuItem("Manage DLC") - { - TooltipText = "Open the DLC management window", - }; - _manageDlcMenuItem.Activated += ManageDlc_Clicked; - - // - // _manageCheatMenuItem - // - _manageCheatMenuItem = new MenuItem("Manage Cheats") - { - TooltipText = "Open the Cheat management window", - }; - _manageCheatMenuItem.Activated += ManageCheats_Clicked; - - // - // _openTitleModDirMenuItem - // - _openTitleModDirMenuItem = new MenuItem("Open Mods Directory") - { - TooltipText = "Open the directory which contains Application's Mods.", - }; - _openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked; - - // - // _openTitleSdModDirMenuItem - // - _openTitleSdModDirMenuItem = new MenuItem("Open Atmosphere Mods Directory") - { - TooltipText = "Open the alternative SD card atmosphere directory which contains the Application's Mods.", - }; - _openTitleSdModDirMenuItem.Activated += OpenTitleSdModDir_Clicked; - - // - // _extractSubMenu - // - _extractSubMenu = new Menu(); - - // - // _extractMenuItem - // - _extractMenuItem = new MenuItem("Extract Data") - { - Submenu = _extractSubMenu - }; - - // - // _extractRomFsMenuItem - // - _extractRomFsMenuItem = new MenuItem("RomFS") - { - TooltipText = "Extract the RomFS section from Application's current config (including updates).", - }; - _extractRomFsMenuItem.Activated += ExtractRomFs_Clicked; - - // - // _extractExeFsMenuItem - // - _extractExeFsMenuItem = new MenuItem("ExeFS") - { - TooltipText = "Extract the ExeFS section from Application's current config (including updates).", - }; - _extractExeFsMenuItem.Activated += ExtractExeFs_Clicked; - - // - // _extractLogoMenuItem - // - _extractLogoMenuItem = new MenuItem("Logo") - { - TooltipText = "Extract the Logo section from Application's current config (including updates).", - }; - _extractLogoMenuItem.Activated += ExtractLogo_Clicked; - - // - // _manageSubMenu - // - _manageSubMenu = new Menu(); - - // - // _manageCacheMenuItem - // - _manageCacheMenuItem = new MenuItem("Cache Management") - { - Submenu = _manageSubMenu, - }; - - // - // _purgePtcCacheMenuItem - // - _purgePtcCacheMenuItem = new MenuItem("Queue PPTC Rebuild") - { - TooltipText = "Trigger PPTC to rebuild at boot time on the next game launch.", - }; - _purgePtcCacheMenuItem.Activated += PurgePtcCache_Clicked; - - // - // _purgeShaderCacheMenuItem - // - _purgeShaderCacheMenuItem = new MenuItem("Purge Shader Cache") - { - TooltipText = "Delete the Application's shader cache.", - }; - _purgeShaderCacheMenuItem.Activated += PurgeShaderCache_Clicked; - - // - // _openPtcDirMenuItem - // - _openPtcDirMenuItem = new MenuItem("Open PPTC Directory") - { - TooltipText = "Open the directory which contains the Application's PPTC cache.", - }; - _openPtcDirMenuItem.Activated += OpenPtcDir_Clicked; - - // - // _openShaderCacheDirMenuItem - // - _openShaderCacheDirMenuItem = new MenuItem("Open Shader Cache Directory") - { - TooltipText = "Open the directory which contains the Application's shader cache.", - }; - _openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked; - - // - // _createShortcutMenuItem - // - _createShortcutMenuItem = new MenuItem("Create Application Shortcut") - { - TooltipText = OperatingSystem.IsMacOS() ? "Create a shortcut in macOS's Applications folder that launches the selected Application" : "Create a Desktop Shortcut that launches the selected Application." - }; - _createShortcutMenuItem.Activated += CreateShortcut_Clicked; - - ShowComponent(); - } - - private void ShowComponent() - { - _extractSubMenu.Append(_extractExeFsMenuItem); - _extractSubMenu.Append(_extractRomFsMenuItem); - _extractSubMenu.Append(_extractLogoMenuItem); - - _manageSubMenu.Append(_purgePtcCacheMenuItem); - _manageSubMenu.Append(_purgeShaderCacheMenuItem); - _manageSubMenu.Append(_openPtcDirMenuItem); - _manageSubMenu.Append(_openShaderCacheDirMenuItem); - - Add(_createShortcutMenuItem); - Add(new SeparatorMenuItem()); - Add(_openSaveUserDirMenuItem); - Add(_openSaveDeviceDirMenuItem); - Add(_openSaveBcatDirMenuItem); - Add(new SeparatorMenuItem()); - Add(_manageTitleUpdatesMenuItem); - Add(_manageDlcMenuItem); - Add(_manageCheatMenuItem); - Add(_openTitleModDirMenuItem); - Add(_openTitleSdModDirMenuItem); - Add(new SeparatorMenuItem()); - Add(_manageCacheMenuItem); - Add(_extractMenuItem); - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs deleted file mode 100644 index dc0dd4c5..00000000 --- a/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs +++ /dev/null @@ -1,644 +0,0 @@ -using Gtk; -using LibHac; -using LibHac.Account; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Fs.Shim; -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.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.UI.App.Common; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using Ryujinx.UI.Windows; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Threading; - -namespace Ryujinx.UI.Widgets -{ - public partial class GameTableContextMenu : Menu - { - private readonly MainWindow _parent; - private readonly VirtualFileSystem _virtualFileSystem; - private readonly AccountManager _accountManager; - private readonly HorizonClient _horizonClient; - private readonly BlitStruct<ApplicationControlProperty> _controlData; - - private readonly string _titleFilePath; - private readonly string _titleName; - private readonly string _titleIdText; - private readonly ulong _titleId; - - private MessageDialog _dialog; - private bool _cancel; - - public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData) - { - _parent = parent; - - InitializeComponent(); - - _virtualFileSystem = virtualFileSystem; - _accountManager = accountManager; - _horizonClient = horizonClient; - _titleFilePath = titleFilePath; - _titleName = titleName; - _titleIdText = titleId; - _controlData = controlData; - - if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId)) - { - GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id"); - - return; - } - - _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; - _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; - _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; - - string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); - bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; - - _extractRomFsMenuItem.Sensitive = hasNca; - _extractExeFsMenuItem.Sensitive = hasNca; - _extractLogoMenuItem.Sensitive = hasNca; - - _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild; - - PopupAtPointer(null); - } - - private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId) - { - saveDataId = default; - - Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); - - if (ResultFs.TargetNotFound.Includes(result)) - { - ref ApplicationControlProperty control = ref controlHolder.Value; - - Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]"); - - if (Utilities.IsZeros(controlHolder.ByteSpan)) - { - // If the current application doesn't have a loaded control property, create a dummy one - // and set the savedata sizes so a user savedata will be created. - control = ref new BlitStruct<ApplicationControlProperty>(1).Value; - - // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. - control.UserAccountSaveDataSize = 0x4000; - control.UserAccountSaveDataJournalSize = 0x4000; - - Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); - } - - Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); - - result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user); - - if (result.IsFailure()) - { - GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); - - return false; - } - - // Try to find the savedata again after creating it - result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); - } - - if (result.IsSuccess()) - { - saveDataId = saveDataInfo.SaveDataId; - - return true; - } - - GtkDialog.CreateErrorDialog($"There was an error finding the specified savedata: {result.ToStringWithName()}"); - - return false; - } - - private void OpenSaveDir(in SaveDataFilter saveDataFilter) - { - if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId)) - { - return; - } - - string saveRootPath = System.IO.Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); - - if (!Directory.Exists(saveRootPath)) - { - // Inconsistent state. Create the directory - Directory.CreateDirectory(saveRootPath); - } - - string committedPath = System.IO.Path.Combine(saveRootPath, "0"); - string workingPath = System.IO.Path.Combine(saveRootPath, "1"); - - // If the committed directory exists, that path will be loaded the next time the savedata is mounted - if (Directory.Exists(committedPath)) - { - OpenHelper.OpenFolder(committedPath); - } - else - { - // If the working directory exists and the committed directory doesn't, - // the working directory will be loaded the next time the savedata is mounted - if (!Directory.Exists(workingPath)) - { - Directory.CreateDirectory(workingPath); - } - - OpenHelper.OpenFolder(workingPath); - } - } - - private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) - { - FileChooserNative fileChooser = new("Choose the folder to extract into", _parent, FileChooserAction.SelectFolder, "Extract", "Cancel"); - - ResponseType response = (ResponseType)fileChooser.Run(); - string destination = fileChooser.Filename; - - fileChooser.Dispose(); - - if (response == ResponseType.Accept) - { - Thread extractorThread = new(() => - { - Gtk.Application.Invoke(delegate - { - _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) - { - Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), - SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", - WindowPosition = WindowPosition.Center, - }; - - int dialogResponse = _dialog.Run(); - if (dialogResponse == (int)ResponseType.Cancel || dialogResponse == (int)ResponseType.DeleteEvent) - { - _cancel = true; - _dialog.Dispose(); - } - }); - - using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read); - - Nca mainNca = null; - Nca patchNca = null; - - if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") || - (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") || - (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci")) - { - IFileSystem pfs; - - if (System.IO.Path.GetExtension(_titleFilePath) == ".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<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Release().AsStorage()); - - 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 (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca") - { - mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage()); - } - - if (mainNca == null) - { - Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA is not present in the selected file."); - - Gtk.Application.Invoke(delegate - { - GtkDialog.CreateErrorDialog("Extraction failure. The main NCA is not present in the selected file."); - }); - - return; - } - - (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); - - if (updatePatchNca != null) - { - patchNca = updatePatchNca; - } - - int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType); - - bool sectionExistsInPatch = false; - - if (patchNca != null) - { - sectionExistsInPatch = patchNca.CanOpenSection(index); - } - - IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) - : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); - - FileSystemClient fsClient = _horizonClient.Fs; - - string source = DateTime.Now.ToFileTime().ToString()[10..]; - string output = DateTime.Now.ToFileTime().ToString()[10..]; - - using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem); - using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination)); - - fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); - fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); - - (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/"); - - if (!canceled) - { - if (resultCode.Value.IsFailure()) - { - Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); - - Gtk.Application.Invoke(delegate - { - _dialog?.Dispose(); - - GtkDialog.CreateErrorDialog("Extraction failed. Read the log file for further information."); - }); - } - else if (resultCode.Value.IsSuccess()) - { - Gtk.Application.Invoke(delegate - { - _dialog?.Dispose(); - - MessageDialog dialog = new(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) - { - Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), - SecondaryText = "Extraction completed successfully.", - WindowPosition = WindowPosition.Center, - }; - - dialog.Run(); - dialog.Dispose(); - }); - } - } - - fsClient.Unmount(source.ToU8Span()); - fsClient.Unmount(output.ToU8Span()); - }) - { - Name = "GUI.NcaSectionExtractorThread", - IsBackground = true, - }; - extractorThread.Start(); - } - } - - private (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath) - { - Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); - if (rc.IsFailure()) - { - return (rc, false); - } - - using (sourceHandle) - { - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) - { - if (_cancel) - { - return (null, true); - } - - string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); - string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); - - if (entry.Type == DirectoryEntryType.Directory) - { - fs.EnsureDirectoryExists(subDstPath); - - (Result? result, bool canceled) = CopyDirectory(fs, subSrcPath, subDstPath); - if (canceled || result.Value.IsFailure()) - { - return (result, canceled); - } - } - - if (entry.Type == DirectoryEntryType.File) - { - fs.CreateOrOverwriteFile(subDstPath, entry.Size); - - rc = CopyFile(fs, subSrcPath, subDstPath); - if (rc.IsFailure()) - { - return (rc, false); - } - } - } - } - - return (Result.Success, false); - } - - public static Result CopyFile(FileSystemClient fs, string sourcePath, string destPath) - { - Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); - if (rc.IsFailure()) - { - return rc; - } - - using (sourceHandle) - { - rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend); - if (rc.IsFailure()) - { - return rc; - } - - using (destHandle) - { - const int MaxBufferSize = 1024 * 1024; - - rc = fs.GetFileSize(out long fileSize, sourceHandle); - if (rc.IsFailure()) - { - return rc; - } - - int bufferSize = (int)Math.Min(MaxBufferSize, fileSize); - - byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); - try - { - for (long offset = 0; offset < fileSize; offset += bufferSize) - { - int toRead = (int)Math.Min(fileSize - offset, bufferSize); - Span<byte> buf = buffer.AsSpan(0, toRead); - - rc = fs.ReadFile(out long _, sourceHandle, offset, buf); - if (rc.IsFailure()) - { - return rc; - } - - rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); - if (rc.IsFailure()) - { - return rc; - } - } - } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - - rc = fs.FlushFile(destHandle); - if (rc.IsFailure()) - { - return rc; - } - } - } - - return Result.Success; - } - - // - // Events - // - private void OpenSaveUserDir_Clicked(object sender, EventArgs args) - { - var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); - var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default); - - OpenSaveDir(in saveDataFilter); - } - - private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args) - { - var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default); - - OpenSaveDir(in saveDataFilter); - } - - private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) - { - var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default); - - OpenSaveDir(in saveDataFilter); - } - - private void ManageTitleUpdates_Clicked(object sender, EventArgs args) - { - new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show(); - } - - private void ManageDlc_Clicked(object sender, EventArgs args) - { - new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show(); - } - - private void ManageCheats_Clicked(object sender, EventArgs args) - { - new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show(); - } - - private void OpenTitleModDir_Clicked(object sender, EventArgs args) - { - string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText); - - OpenHelper.OpenFolder(titleModsPath); - } - - private void OpenTitleSdModDir_Clicked(object sender, EventArgs args) - { - string sdModsBasePath = ModLoader.GetSdModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText); - - OpenHelper.OpenFolder(titleModsPath); - } - - private void ExtractRomFs_Clicked(object sender, EventArgs args) - { - ExtractSection(NcaSectionType.Data); - } - - private void ExtractExeFs_Clicked(object sender, EventArgs args) - { - ExtractSection(NcaSectionType.Code); - } - - private void ExtractLogo_Clicked(object sender, EventArgs args) - { - ExtractSection(NcaSectionType.Logo); - } - - private void OpenPtcDir_Clicked(object sender, EventArgs args) - { - string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu"); - - string mainPath = System.IO.Path.Combine(ptcDir, "0"); - string backupPath = System.IO.Path.Combine(ptcDir, "1"); - - if (!Directory.Exists(ptcDir)) - { - Directory.CreateDirectory(ptcDir); - Directory.CreateDirectory(mainPath); - Directory.CreateDirectory(backupPath); - } - - OpenHelper.OpenFolder(ptcDir); - } - - private void OpenShaderCacheDir_Clicked(object sender, EventArgs args) - { - string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"); - - if (!Directory.Exists(shaderCacheDir)) - { - Directory.CreateDirectory(shaderCacheDir); - } - - OpenHelper.OpenFolder(shaderCacheDir); - } - - private void PurgePtcCache_Clicked(object sender, EventArgs args) - { - DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0")); - DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1")); - - MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?"); - - List<FileInfo> cacheFiles = new(); - - if (mainDir.Exists) - { - cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); - } - - if (backupDir.Exists) - { - cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); - } - - if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) - { - foreach (FileInfo file in cacheFiles) - { - try - { - file.Delete(); - } - catch (Exception e) - { - GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}"); - } - } - } - - warningDialog.Dispose(); - } - - private void PurgeShaderCache_Clicked(object sender, EventArgs args) - { - DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader")); - - using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?"); - - List<DirectoryInfo> oldCacheDirectories = new(); - List<FileInfo> newCacheFiles = new(); - - if (shaderCacheDir.Exists) - { - oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); - } - - if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && warningDialog.Run() == (int)ResponseType.Yes) - { - foreach (DirectoryInfo directory in oldCacheDirectories) - { - try - { - directory.Delete(true); - } - catch (Exception e) - { - GtkDialog.CreateErrorDialog($"Error purging shader cache at {directory.Name}: {e}"); - } - } - - foreach (FileInfo file in newCacheFiles) - { - try - { - file.Delete(); - } - catch (Exception e) - { - GtkDialog.CreateErrorDialog($"Error purging shader cache at {file.Name}: {e}"); - } - } - } - } - - private void CreateShortcut_Clicked(object sender, EventArgs args) - { - byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language); - ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/GtkDialog.cs b/src/Ryujinx/UI/Widgets/GtkDialog.cs deleted file mode 100644 index 567f9ad6..00000000 --- a/src/Ryujinx/UI/Widgets/GtkDialog.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Gtk; -using Ryujinx.Common.Logging; -using Ryujinx.UI.Common.Configuration; -using System.Collections.Generic; -using System.Reflection; - -namespace Ryujinx.UI.Widgets -{ - internal class GtkDialog : MessageDialog - { - private static bool _isChoiceDialogOpen; - - private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) - : base(null, DialogFlags.Modal, messageType, buttonsType, null) - { - Title = title; - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - Text = mainText; - SecondaryText = secondaryText; - WindowPosition = WindowPosition.Center; - SecondaryUseMarkup = true; - - Response += GtkDialog_Response; - - SetSizeRequest(200, 20); - } - - private void GtkDialog_Response(object sender, ResponseArgs args) - { - Dispose(); - } - - internal static void CreateInfoDialog(string mainText, string secondaryText) - { - new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run(); - } - - internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText) - { - new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run(); - } - - internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText) - { - return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None); - } - - internal static void CreateWarningDialog(string mainText, string secondaryText) - { - new GtkDialog("Ryujinx - Warning", mainText, secondaryText, MessageType.Warning).Run(); - } - - internal static void CreateErrorDialog(string errorMessage) - { - Logger.Error?.Print(LogClass.Application, errorMessage); - - new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run(); - } - - internal static MessageDialog CreateConfirmationDialog(string mainText, string secondaryText = "") - { - return new GtkDialog("Ryujinx - Confirmation", mainText, secondaryText, MessageType.Question, ButtonsType.YesNo); - } - - internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText) - { - if (_isChoiceDialogOpen) - { - return false; - } - - _isChoiceDialogOpen = true; - - ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run(); - - _isChoiceDialogOpen = false; - - return response == ResponseType.Yes; - } - - internal static ResponseType CreateCustomDialog(string title, string mainText, string secondaryText, Dictionary<int, string> buttons, MessageType messageType = MessageType.Other) - { - GtkDialog gtkDialog = new(title, mainText, secondaryText, messageType, ButtonsType.None); - - foreach (var button in buttons) - { - gtkDialog.AddButton(button.Value, button.Key); - } - - return (ResponseType)gtkDialog.Run(); - } - - internal static string CreateInputDialog(Window parent, string title, string mainText, uint inputMax) - { - GtkInputDialog gtkDialog = new(parent, title, mainText, inputMax); - ResponseType response = (ResponseType)gtkDialog.Run(); - string responseText = gtkDialog.InputEntry.Text.TrimEnd(); - - gtkDialog.Dispose(); - - if (response == ResponseType.Ok) - { - return responseText; - } - - return ""; - } - - internal static bool CreateExitDialog() - { - return CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to close Ryujinx?", "All unsaved data will be lost!"); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/GtkInputDialog.cs b/src/Ryujinx/UI/Widgets/GtkInputDialog.cs deleted file mode 100644 index fd85248b..00000000 --- a/src/Ryujinx/UI/Widgets/GtkInputDialog.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Gtk; - -namespace Ryujinx.UI.Widgets -{ - public class GtkInputDialog : MessageDialog - { - public Entry InputEntry { get; } - - public GtkInputDialog(Window parent, string title, string mainText, uint inputMax) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.OkCancel, null) - { - SetDefaultSize(300, 0); - - Title = title; - - Label mainTextLabel = new() - { - Text = mainText, - }; - - InputEntry = new Entry - { - MaxLength = (int)inputMax, - }; - - Label inputMaxTextLabel = new() - { - Text = $"(Max length: {inputMax})", - }; - - ((Box)MessageArea).PackStart(mainTextLabel, true, true, 0); - ((Box)MessageArea).PackStart(InputEntry, true, true, 5); - ((Box)MessageArea).PackStart(inputMaxTextLabel, true, true, 0); - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/ProfileDialog.cs b/src/Ryujinx/UI/Widgets/ProfileDialog.cs deleted file mode 100644 index f8aa6345..00000000 --- a/src/Ryujinx/UI/Widgets/ProfileDialog.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Gtk; -using Ryujinx.UI.Common.Configuration; -using System; -using System.Reflection; -using GUI = Gtk.Builder.ObjectAttribute; - -namespace Ryujinx.UI.Widgets -{ - public class ProfileDialog : Dialog - { - public string FileName { get; private set; } - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] Entry _profileEntry; - [GUI] Label _errorMessage; -#pragma warning restore CS0649, IDE0044 - - public ProfileDialog() : this(new Builder("Ryujinx.UI.Widgets.ProfileDialog.glade")) { } - - private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) - { - builder.Autoconnect(this); - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - } - - private void OkToggle_Activated(object sender, EventArgs args) - { - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - - bool validFileName = true; - - foreach (char invalidChar in System.IO.Path.GetInvalidFileNameChars()) - { - if (_profileEntry.Text.Contains(invalidChar)) - { - validFileName = false; - } - } - - if (validFileName && !string.IsNullOrEmpty(_profileEntry.Text)) - { - FileName = $"{_profileEntry.Text}.json"; - - Respond(ResponseType.Ok); - } - else - { - _errorMessage.Text = "The file name contains invalid characters. Please try again."; - } - } - - private void CancelToggle_Activated(object sender, EventArgs args) - { - Respond(ResponseType.Cancel); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/ProfileDialog.glade b/src/Ryujinx/UI/Widgets/ProfileDialog.glade deleted file mode 100644 index adaf6608..00000000 --- a/src/Ryujinx/UI/Widgets/ProfileDialog.glade +++ /dev/null @@ -1,124 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkDialog" id="_profileDialog"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - Profile Dialog</property> - <property name="modal">True</property> - <property name="window_position">center</property> - <property name="default_width">400</property> - <property name="type_hint">dialog</property> - <child> - <placeholder/> - </child> - <child internal-child="vbox"> - <object class="GtkBox"> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkButtonBox"> - <property name="can_focus">False</property> - <property name="layout_style">end</property> - <child> - <object class="GtkToggleButton" id="OkToggle"> - <property name="label" translatable="yes">OK</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <signal name="toggled" handler="OkToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="CancelToggle"> - <property name="label" translatable="yes">Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <signal name="toggled" handler="CancelToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="margin_top">20</property> - <property name="margin_bottom">10</property> - <property name="label" translatable="yes">Enter a name for the new profile:</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_profileEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">20</property> - <property name="margin_right">20</property> - <property name="margin_top">20</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="_errorMessage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_left">20</property> - <property name="margin_right">10</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <attributes> - <attribute name="foreground" value="#ffff00000000"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs b/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs deleted file mode 100644 index 6470790e..00000000 --- a/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Gtk; - -namespace Ryujinx.UI.Widgets -{ - public class RawInputToTextEntry : Entry - { - public void SendKeyPressEvent(object o, KeyPressEventArgs args) - { - base.OnKeyPressEvent(args.Event); - } - - public void SendKeyReleaseEvent(object o, KeyReleaseEventArgs args) - { - base.OnKeyReleaseEvent(args.Event); - } - - public void SendButtonPressEvent(object o, ButtonPressEventArgs args) - { - base.OnButtonPressEvent(args.Event); - } - - public void SendButtonReleaseEvent(object o, ButtonReleaseEventArgs args) - { - base.OnButtonReleaseEvent(args.Event); - } - } -} diff --git a/src/Ryujinx/UI/Widgets/UserErrorDialog.cs b/src/Ryujinx/UI/Widgets/UserErrorDialog.cs deleted file mode 100644 index f0b55cd8..00000000 --- a/src/Ryujinx/UI/Widgets/UserErrorDialog.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Gtk; -using Ryujinx.UI.Common; -using Ryujinx.UI.Common.Helper; - -namespace Ryujinx.UI.Widgets -{ - internal class UserErrorDialog : MessageDialog - { - private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; - private const int OkResponseId = 0; - private const int SetupGuideResponseId = 1; - - private readonly UserError _userError; - - private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null) - { - _userError = error; - - WindowPosition = WindowPosition.Center; - SecondaryUseMarkup = true; - - Response += UserErrorDialog_Response; - - SetSizeRequest(120, 50); - - AddButton("OK", OkResponseId); - - bool isInSetupGuide = IsCoveredBySetupGuide(error); - - if (isInSetupGuide) - { - AddButton("Open the Setup Guide", SetupGuideResponseId); - } - - string errorCode = GetErrorCode(error); - - SecondaryUseMarkup = true; - - Title = $"Ryujinx error ({errorCode})"; - Text = $"{errorCode}: {GetErrorTitle(error)}"; - SecondaryText = GetErrorDescription(error); - - if (isInSetupGuide) - { - SecondaryText += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>"; - } - } - - private static string GetErrorCode(UserError error) - { - return $"RYU-{(uint)error:X4}"; - } - - private static string GetErrorTitle(UserError error) - { - return error switch - { - UserError.NoKeys => "Keys not found", - UserError.NoFirmware => "Firmware not found", - UserError.FirmwareParsingFailed => "Firmware parsing error", - UserError.ApplicationNotFound => "Application not found", - UserError.Unknown => "Unknown error", - _ => "Undefined error", - }; - } - - private static string GetErrorDescription(UserError error) - { - return error switch - { - UserError.NoKeys => "Ryujinx was unable to find your 'prod.keys' file", - UserError.NoFirmware => "Ryujinx was unable to find any firmwares installed", - UserError.FirmwareParsingFailed => "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", - UserError.ApplicationNotFound => "Ryujinx couldn't find a valid application at the given path.", - UserError.Unknown => "An unknown error occured!", - _ => "An undefined error occured! This shouldn't happen, please contact a dev!", - }; - } - - private static bool IsCoveredBySetupGuide(UserError error) - { - return error switch - { - UserError.NoKeys or - UserError.NoFirmware or - UserError.FirmwareParsingFailed => true, - _ => false, - }; - } - - private static string GetSetupGuideUrl(UserError error) - { - if (!IsCoveredBySetupGuide(error)) - { - return null; - } - - return error switch - { - UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", - UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", - _ => SetupGuideUrl, - }; - } - - private void UserErrorDialog_Response(object sender, ResponseArgs args) - { - int responseId = (int)args.ResponseId; - - if (responseId == SetupGuideResponseId) - { - OpenHelper.OpenUrl(GetSetupGuideUrl(_userError)); - } - - Dispose(); - } - - public static void CreateUserErrorDialog(UserError error) - { - new UserErrorDialog(error).Run(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs b/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs deleted file mode 100644 index fd912ef9..00000000 --- a/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs +++ /dev/null @@ -1,511 +0,0 @@ -using Gtk; -using Pango; -using Ryujinx.UI.Common.Configuration; -using System.Reflection; - -namespace Ryujinx.UI.Windows -{ - public partial class AboutWindow : Window - { - private Box _mainBox; - private Box _leftBox; - private Box _logoBox; - private Image _ryujinxLogo; - private Box _logoTextBox; - private Label _ryujinxLabel; - private Label _ryujinxPhoneticLabel; - private EventBox _ryujinxLink; - private Label _ryujinxLinkLabel; - private Label _versionLabel; - private Label _disclaimerLabel; - private EventBox _amiiboApiLink; - private Label _amiiboApiLinkLabel; - private Box _socialBox; - private EventBox _patreonEventBox; - private Box _patreonBox; - private Image _patreonLogo; - private Label _patreonLabel; - private EventBox _githubEventBox; - private Box _githubBox; - private Image _githubLogo; - private Label _githubLabel; - private Box _discordBox; - private EventBox _discordEventBox; - private Image _discordLogo; - private Label _discordLabel; - private EventBox _twitterEventBox; - private Box _twitterBox; - private Image _twitterLogo; - private Label _twitterLabel; - private Separator _separator; - private Box _rightBox; - private Label _aboutLabel; - private Label _aboutDescriptionLabel; - private Label _createdByLabel; - private TextView _createdByText; - private EventBox _contributorsEventBox; - private Label _contributorsLinkLabel; - private Label _patreonNamesLabel; - private ScrolledWindow _patreonNamesScrolled; - private TextView _patreonNamesText; - private EventBox _changelogEventBox; - private Label _changelogLinkLabel; - - private void InitializeComponent() - { - - // - // AboutWindow - // - CanFocus = false; - Resizable = false; - Modal = true; - WindowPosition = WindowPosition.Center; - DefaultWidth = 800; - DefaultHeight = 450; - TypeHint = Gdk.WindowTypeHint.Dialog; - - // - // _mainBox - // - _mainBox = new Box(Orientation.Horizontal, 0); - - // - // _leftBox - // - _leftBox = new Box(Orientation.Vertical, 0) - { - Margin = 15, - MarginStart = 30, - MarginEnd = 0, - }; - - // - // _logoBox - // - _logoBox = new Box(Orientation.Horizontal, 0); - - // - // _ryujinxLogo - // - _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png", 100, 100)) - { - Margin = 10, - MarginStart = 15, - }; - - // - // _logoTextBox - // - _logoTextBox = new Box(Orientation.Vertical, 0); - - // - // _ryujinxLabel - // - _ryujinxLabel = new Label("Ryujinx") - { - MarginTop = 15, - Justify = Justification.Center, - Attributes = new AttrList(), - }; - _ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f)); - - // - // _ryujinxPhoneticLabel - // - _ryujinxPhoneticLabel = new Label("(REE-YOU-JINX)") - { - Justify = Justification.Center, - }; - - // - // _ryujinxLink - // - _ryujinxLink = new EventBox() - { - Margin = 5 - }; - _ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed; - - // - // _ryujinxLinkLabel - // - _ryujinxLinkLabel = new Label("www.ryujinx.org") - { - TooltipText = "Click to open the Ryujinx website in your default browser.", - Justify = Justification.Center, - Attributes = new AttrList(), - }; - _ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _versionLabel - // - _versionLabel = new Label(Program.Version) - { - Expand = true, - Justify = Justification.Center, - Margin = 5, - }; - - // - // _changelogEventBox - // - _changelogEventBox = new EventBox(); - _changelogEventBox.ButtonPressEvent += ChangelogButton_Pressed; - - // - // _changelogLinkLabel - // - _changelogLinkLabel = new Label("View Changelog on GitHub") - { - TooltipText = "Click to open the changelog for this version in your default browser.", - Justify = Justification.Center, - Attributes = new AttrList(), - }; - _changelogLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _disclaimerLabel - // - _disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.") - { - Expand = true, - Justify = Justification.Center, - Margin = 5, - Attributes = new AttrList(), - }; - _disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f)); - - // - // _amiiboApiLink - // - _amiiboApiLink = new EventBox() - { - Margin = 5, - }; - _amiiboApiLink.ButtonPressEvent += AmiiboApiButton_Pressed; - - // - // _amiiboApiLinkLabel - // - _amiiboApiLinkLabel = new Label("AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.") - { - TooltipText = "Click to open the AmiiboAPI website in your default browser.", - Justify = Justification.Center, - Attributes = new AttrList(), - }; - _amiiboApiLinkLabel.Attributes.Insert(new Pango.AttrScale(0.9f)); - - // - // _socialBox - // - _socialBox = new Box(Orientation.Horizontal, 0) - { - Margin = 25, - MarginBottom = 10, - }; - - // - // _patreonEventBox - // - _patreonEventBox = new EventBox() - { - TooltipText = "Click to open the Ryujinx Patreon page in your default browser.", - }; - _patreonEventBox.ButtonPressEvent += PatreonButton_Pressed; - - // - // _patreonBox - // - _patreonBox = new Box(Orientation.Vertical, 0); - - // - // _patreonLogo - // - _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png", 30, 30)) - { - Margin = 10, - }; - - // - // _patreonLabel - // - _patreonLabel = new Label("Patreon") - { - Justify = Justification.Center, - }; - - // - // _githubEventBox - // - _githubEventBox = new EventBox() - { - TooltipText = "Click to open the Ryujinx GitHub page in your default browser.", - }; - _githubEventBox.ButtonPressEvent += GitHubButton_Pressed; - - // - // _githubBox - // - _githubBox = new Box(Orientation.Vertical, 0); - - // - // _githubLogo - // - _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png", 30, 30)) - { - Margin = 10, - }; - - // - // _githubLabel - // - _githubLabel = new Label("GitHub") - { - Justify = Justification.Center, - }; - - // - // _discordBox - // - _discordBox = new Box(Orientation.Vertical, 0); - - // - // _discordEventBox - // - _discordEventBox = new EventBox() - { - TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser.", - }; - _discordEventBox.ButtonPressEvent += DiscordButton_Pressed; - - // - // _discordLogo - // - _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Discord_Light.png", 30, 30)) - { - Margin = 10, - }; - - // - // _discordLabel - // - _discordLabel = new Label("Discord") - { - Justify = Justification.Center, - }; - - // - // _twitterEventBox - // - _twitterEventBox = new EventBox() - { - TooltipText = "Click to open the Ryujinx Twitter page in your default browser.", - }; - _twitterEventBox.ButtonPressEvent += TwitterButton_Pressed; - - // - // _twitterBox - // - _twitterBox = new Box(Orientation.Vertical, 0); - - // - // _twitterLogo - // - _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png", 30, 30)) - { - Margin = 10, - }; - - // - // _twitterLabel - // - _twitterLabel = new Label("Twitter") - { - Justify = Justification.Center, - }; - - // - // _separator - // - _separator = new Separator(Orientation.Vertical) - { - Margin = 15, - }; - - // - // _rightBox - // - _rightBox = new Box(Orientation.Vertical, 0) - { - Margin = 15, - MarginTop = 40, - }; - - // - // _aboutLabel - // - _aboutLabel = new Label("About :") - { - Halign = Align.Start, - Attributes = new AttrList(), - }; - _aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); - _aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _aboutDescriptionLabel - // - _aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" + - "Please support us on Patreon.\n" + - "Get all the latest news on our Twitter or Discord.\n" + - "Developers interested in contributing can find out more on our GitHub or Discord.") - { - Margin = 15, - Halign = Align.Start, - }; - - // - // _createdByLabel - // - _createdByLabel = new Label("Maintained by :") - { - Halign = Align.Start, - Attributes = new AttrList(), - }; - _createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); - _createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _createdByText - // - _createdByText = new TextView() - { - WrapMode = Gtk.WrapMode.Word, - Editable = false, - CursorVisible = false, - Margin = 15, - MarginEnd = 30, - }; - _createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more..."; - - // - // _contributorsEventBox - // - _contributorsEventBox = new EventBox(); - _contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed; - - // - // _contributorsLinkLabel - // - _contributorsLinkLabel = new Label("See All Contributors...") - { - TooltipText = "Click to open the Contributors page in your default browser.", - MarginEnd = 30, - Halign = Align.End, - Attributes = new AttrList(), - }; - _contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _patreonNamesLabel - // - _patreonNamesLabel = new Label("Supported on Patreon by :") - { - Halign = Align.Start, - Attributes = new AttrList(), - }; - _patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); - _patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); - - // - // _patreonNamesScrolled - // - _patreonNamesScrolled = new ScrolledWindow() - { - Margin = 15, - MarginEnd = 30, - Expand = true, - ShadowType = ShadowType.In, - }; - _patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic); - - // - // _patreonNamesText - // - _patreonNamesText = new TextView() - { - WrapMode = Gtk.WrapMode.Word, - }; - _patreonNamesText.Buffer.Text = "Loading..."; - _patreonNamesText.SetProperty("editable", new GLib.Value(false)); - - ShowComponent(); - } - - private void ShowComponent() - { - _logoBox.Add(_ryujinxLogo); - - _ryujinxLink.Add(_ryujinxLinkLabel); - - _logoTextBox.Add(_ryujinxLabel); - _logoTextBox.Add(_ryujinxPhoneticLabel); - _logoTextBox.Add(_ryujinxLink); - - _logoBox.Add(_logoTextBox); - - _amiiboApiLink.Add(_amiiboApiLinkLabel); - - _patreonBox.Add(_patreonLogo); - _patreonBox.Add(_patreonLabel); - _patreonEventBox.Add(_patreonBox); - - _githubBox.Add(_githubLogo); - _githubBox.Add(_githubLabel); - _githubEventBox.Add(_githubBox); - - _discordBox.Add(_discordLogo); - _discordBox.Add(_discordLabel); - _discordEventBox.Add(_discordBox); - - _twitterBox.Add(_twitterLogo); - _twitterBox.Add(_twitterLabel); - _twitterEventBox.Add(_twitterBox); - - _socialBox.Add(_patreonEventBox); - _socialBox.Add(_githubEventBox); - _socialBox.Add(_discordEventBox); - _socialBox.Add(_twitterEventBox); - - _changelogEventBox.Add(_changelogLinkLabel); - - _leftBox.Add(_logoBox); - _leftBox.Add(_versionLabel); - _leftBox.Add(_changelogEventBox); - _leftBox.Add(_disclaimerLabel); - _leftBox.Add(_amiiboApiLink); - _leftBox.Add(_socialBox); - - _contributorsEventBox.Add(_contributorsLinkLabel); - _patreonNamesScrolled.Add(_patreonNamesText); - - _rightBox.Add(_aboutLabel); - _rightBox.Add(_aboutDescriptionLabel); - _rightBox.Add(_createdByLabel); - _rightBox.Add(_createdByText); - _rightBox.Add(_contributorsEventBox); - _rightBox.Add(_patreonNamesLabel); - _rightBox.Add(_patreonNamesScrolled); - - _mainBox.Add(_leftBox); - _mainBox.Add(_separator); - _mainBox.Add(_rightBox); - - Add(_mainBox); - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/AboutWindow.axaml b/src/Ryujinx/UI/Windows/AboutWindow.axaml new file mode 100644 index 00000000..69fa8251 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AboutWindow.axaml @@ -0,0 +1,270 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Windows.AboutWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels" + Width="550" + Height="260" + Margin="0,-12,0,0" + d:DesignHeight="260" + d:DesignWidth="550" + x:DataType="viewModel:AboutWindowViewModel" + Focusable="True" + mc:Ignorable="d"> + <Design.DataContext> + <viewModel:AboutWindowViewModel /> + </Design.DataContext> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Grid + Grid.Column="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <StackPanel + Grid.Row="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Spacing="10"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <StackPanel + Grid.Column="1" + Orientation="Horizontal" + HorizontalAlignment="Center" + Spacing="10"> + <Image + Height="80" + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" + HorizontalAlignment="Center" + IsHitTestVisible="True" /> + <WrapPanel + HorizontalAlignment="Right" + VerticalAlignment="Center" + Orientation="Vertical"> + <TextBlock + FontSize="28" + FontWeight="Bold" + Text="Ryujinx" + TextAlignment="Start" + Width="110" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + <TextBlock + FontSize="11" + Text="(REE-YOU-JINX)" + TextAlignment="Start" + Width="110" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </WrapPanel> + </StackPanel> + </Grid> + <TextBlock + HorizontalAlignment="Center" + VerticalAlignment="Center" + FontSize="10" + LineHeight="12" + Text="{Binding Version}" + TextAlignment="Center" /> + <Button + Padding="5" + HorizontalAlignment="Center" + Background="Transparent" + Click="Button_OnClick" + Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"> + <TextBlock + FontSize="10" + Text="{locale:Locale AboutChangelogButton}" + TextAlignment="Center" + ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" /> + </Button> + </StackPanel> + <StackPanel + Grid.Row="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Spacing="10"> + <TextBlock + Width="200" + HorizontalAlignment="Center" + FontSize="10" + LineHeight="12" + Text="{locale:Locale AboutDisclaimerMessage}" + TextAlignment="Center" + TextWrapping="Wrap" /> + <TextBlock + Name="AmiiboLabel" + Width="200" + HorizontalAlignment="Center" + FontSize="10" + LineHeight="12" + PointerPressed="AmiiboLabel_OnPointerPressed" + Text="{locale:Locale AboutAmiiboDisclaimerMessage}" + TextAlignment="Center" + TextWrapping="Wrap" /> + <StackPanel + HorizontalAlignment="Center" + Orientation="Horizontal" + Spacing="10"> + <Button + MinWidth="30" + MinHeight="30" + MaxWidth="30" + MaxHeight="30" + Padding="8" + Background="Transparent" + Click="Button_OnClick" + CornerRadius="15" + Tag="https://www.patreon.com/ryujinx" + ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> + <Image Source="{Binding PatreonLogo}" /> + </Button> + <Button + MinWidth="30" + MinHeight="30" + MaxWidth="30" + MaxHeight="30" + Padding="8" + Background="Transparent" + Click="Button_OnClick" + CornerRadius="15" + Tag="https://github.com/Ryujinx/Ryujinx" + ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> + <Image Source="{Binding GithubLogo}" /> + </Button> + <Button + MinWidth="30" + MinHeight="30" + MaxWidth="30" + MaxHeight="30" + Padding="8" + Background="Transparent" + Click="Button_OnClick" + CornerRadius="15" + Tag="https://discordapp.com/invite/N2FmfVc" + ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> + <Image Source="{Binding DiscordLogo}" /> + </Button> + <Button + MinWidth="30" + MinHeight="30" + MaxWidth="30" + MaxHeight="30" + Padding="8" + Background="Transparent" + Click="Button_OnClick" + CornerRadius="15" + Tag="https://twitter.com/RyujinxEmu" + ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> + <Image Source="{Binding TwitterLogo}" /> + </Button> + <Button + MinWidth="30" + MinHeight="30" + MaxWidth="30" + MaxHeight="30" + Padding="8" + Background="Transparent" + Click="Button_OnClick" + CornerRadius="15" + Tag="https://www.ryujinx.org" + ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}"> + <ui:SymbolIcon Foreground="{DynamicResource ThemeForegroundColor}" Symbol="Link" /> + </Button> + </StackPanel> + </StackPanel> + </Grid> + <Border + Grid.Column="1" + Width="1" + Margin="20,0" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="1,0,0,0" /> + <Grid + Grid.Column="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <StackPanel + Grid.Row="0" + Margin="0,10,0,0" + Spacing="2"> + <TextBlock + FontSize="15" + FontWeight="Bold" + Text="{locale:Locale AboutRyujinxAboutTitle}" /> + <TextBlock + FontSize="10" + Text="{locale:Locale AboutRyujinxAboutContent}" + TextWrapping="Wrap" /> + </StackPanel> + <StackPanel + Grid.Row="1" + Margin="0,10,0,0" + Spacing="2"> + <TextBlock + FontSize="15" + FontWeight="Bold" + Text="{locale:Locale AboutRyujinxMaintainersTitle}" /> + <TextBlock + FontSize="10" + Text="{Binding Developers}" + TextWrapping="Wrap" /> + <Button + Padding="5" + HorizontalAlignment="Left" + Background="Transparent" + Click="Button_OnClick" + Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"> + <TextBlock + FontSize="10" + Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" + TextAlignment="End" + ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" /> + </Button> + </StackPanel> + <StackPanel + Grid.Row="2" + Margin="0,10,0,0" + Spacing="2"> + <TextBlock + FontSize="15" + FontWeight="Bold" + Text="{locale:Locale AboutRyujinxSupprtersTitle}" /> + <ScrollViewer + Height="70" + HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Visible"> + <TextBlock + Name="SupportersTextBlock" + VerticalAlignment="Top" + FontSize="10" + Text="{Binding Supporters}" + TextWrapping="Wrap" /> + </ScrollViewer> + </StackPanel> + </Grid> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs b/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs new file mode 100644 index 00000000..c32661b0 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs @@ -0,0 +1,63 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class AboutWindow : UserControl + { + public AboutWindow() + { + DataContext = new AboutWindowViewModel(); + + InitializeComponent(); + } + + public static async Task Show() + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], + Content = new AboutWindow(), + }; + + Style closeButton = new(x => x.Name("CloseButton")); + closeButton.Setters.Add(new Setter(WidthProperty, 80d)); + + Style closeButtonParent = new(x => x.Name("CommandSpace")); + closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Right)); + + contentDialog.Styles.Add(closeButton); + contentDialog.Styles.Add(closeButtonParent); + + await ContentDialogHelper.ShowAsync(contentDialog); + } + + private void Button_OnClick(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + OpenHelper.OpenUrl(button.Tag.ToString()); + } + } + + private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is TextBlock) + { + OpenHelper.OpenUrl("https://amiiboapi.com"); + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/AboutWindow.cs b/src/Ryujinx/UI/Windows/AboutWindow.cs deleted file mode 100644 index f4bb533c..00000000 --- a/src/Ryujinx/UI/Windows/AboutWindow.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Gtk; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Helper; -using System.Net.Http; -using System.Net.NetworkInformation; -using System.Reflection; -using System.Threading.Tasks; - -namespace Ryujinx.UI.Windows -{ - public partial class AboutWindow : Window - { - public AboutWindow() : base($"Ryujinx {Program.Version} - About") - { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(OpenHelper)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - InitializeComponent(); - - _ = DownloadPatronsJson(); - } - - private async Task DownloadPatronsJson() - { - if (!NetworkInterface.GetIsNetworkAvailable()) - { - _patreonNamesText.Buffer.Text = "Connection Error."; - } - - HttpClient httpClient = new(); - - try - { - string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); - - _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)); - } - catch - { - _patreonNamesText.Buffer.Text = "API Error."; - } - } - - // - // Events - // - private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://ryujinx.org"); - } - - private void AmiiboApiButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://amiiboapi.com"); - } - - private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://www.patreon.com/ryujinx"); - } - - private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx"); - } - - private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc"); - } - - private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu"); - } - - private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); - } - - private void ChangelogButton_Pressed(object sender, ButtonPressEventArgs args) - { - OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"); - } - } -} diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs deleted file mode 100644 index 3bf73318..00000000 --- a/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Gtk; - -namespace Ryujinx.UI.Windows -{ - public partial class AmiiboWindow : Window - { - private Box _mainBox; - private ButtonBox _buttonBox; - private Button _scanButton; - private Button _cancelButton; - private CheckButton _randomUuidCheckBox; - private Box _amiiboBox; - private Box _amiiboHeadBox; - private Box _amiiboSeriesBox; - private Label _amiiboSeriesLabel; - private ComboBoxText _amiiboSeriesComboBox; - private Box _amiiboCharsBox; - private Label _amiiboCharsLabel; - private ComboBoxText _amiiboCharsComboBox; - private CheckButton _showAllCheckBox; - private Image _amiiboImage; - private Label _gameUsageLabel; - - private void InitializeComponent() - { - // - // AmiiboWindow - // - CanFocus = false; - Resizable = false; - Modal = true; - WindowPosition = WindowPosition.Center; - DefaultWidth = 600; - DefaultHeight = 470; - TypeHint = Gdk.WindowTypeHint.Dialog; - - // - // _mainBox - // - _mainBox = new Box(Orientation.Vertical, 2); - - // - // _buttonBox - // - _buttonBox = new ButtonBox(Orientation.Horizontal) - { - Margin = 20, - LayoutStyle = ButtonBoxStyle.End, - }; - - // - // _scanButton - // - _scanButton = new Button() - { - Label = "Scan It!", - CanFocus = true, - ReceivesDefault = true, - MarginStart = 10, - }; - _scanButton.Clicked += ScanButton_Pressed; - - // - // _randomUuidCheckBox - // - _randomUuidCheckBox = new CheckButton() - { - Label = "Hack: Use Random Tag Uuid", - TooltipText = "This allows multiple scans of a single Amiibo.\n(used in The Legend of Zelda: Breath of the Wild)", - }; - - // - // _cancelButton - // - _cancelButton = new Button() - { - Label = "Cancel", - CanFocus = true, - ReceivesDefault = true, - MarginStart = 10, - }; - _cancelButton.Clicked += CancelButton_Pressed; - - // - // _amiiboBox - // - _amiiboBox = new Box(Orientation.Vertical, 0); - - // - // _amiiboHeadBox - // - _amiiboHeadBox = new Box(Orientation.Horizontal, 0) - { - Margin = 20, - Hexpand = true, - }; - - // - // _amiiboSeriesBox - // - _amiiboSeriesBox = new Box(Orientation.Horizontal, 0) - { - Hexpand = true, - }; - - // - // _amiiboSeriesLabel - // - _amiiboSeriesLabel = new Label("Amiibo Series:"); - - // - // _amiiboSeriesComboBox - // - _amiiboSeriesComboBox = new ComboBoxText(); - - // - // _amiiboCharsBox - // - _amiiboCharsBox = new Box(Orientation.Horizontal, 0) - { - Hexpand = true, - }; - - // - // _amiiboCharsLabel - // - _amiiboCharsLabel = new Label("Character:"); - - // - // _amiiboCharsComboBox - // - _amiiboCharsComboBox = new ComboBoxText(); - - // - // _showAllCheckBox - // - _showAllCheckBox = new CheckButton() - { - Label = "Show All Amiibo", - }; - - // - // _amiiboImage - // - _amiiboImage = new Image() - { - HeightRequest = 350, - WidthRequest = 350, - }; - - // - // _gameUsageLabel - // - _gameUsageLabel = new Label("") - { - MarginTop = 20, - }; - - ShowComponent(); - } - - private void ShowComponent() - { - _buttonBox.Add(_showAllCheckBox); - _buttonBox.Add(_randomUuidCheckBox); - _buttonBox.Add(_scanButton); - _buttonBox.Add(_cancelButton); - - _amiiboSeriesBox.Add(_amiiboSeriesLabel); - _amiiboSeriesBox.Add(_amiiboSeriesComboBox); - - _amiiboCharsBox.Add(_amiiboCharsLabel); - _amiiboCharsBox.Add(_amiiboCharsComboBox); - - _amiiboHeadBox.Add(_amiiboSeriesBox); - _amiiboHeadBox.Add(_amiiboCharsBox); - - _amiiboBox.PackStart(_amiiboHeadBox, true, true, 0); - _amiiboBox.PackEnd(_gameUsageLabel, false, false, 0); - _amiiboBox.PackEnd(_amiiboImage, false, false, 0); - - _mainBox.Add(_amiiboBox); - _mainBox.PackEnd(_buttonBox, false, false, 0); - - Add(_mainBox); - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.axaml b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml new file mode 100644 index 00000000..c587aa87 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml @@ -0,0 +1,75 @@ +<window:StyleableWindow + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + mc:Ignorable="d" + d:DesignWidth="400" + d:DesignHeight="350" + x:Class="Ryujinx.Ava.UI.Windows.AmiiboWindow" + x:DataType="viewModels:AmiiboWindowViewModel" + CanResize="False" + WindowStartupLocation="CenterOwner" + Width="800" + MinHeight="650" + Height="650" + SizeToContent="Manual" + MinWidth="600" + Focusable="True"> + <Design.DataContext> + <viewModels:AmiiboWindowViewModel /> + </Design.DataContext> + <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid Grid.Row="1" HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboSeriesLabel}" /> + <ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" /> + </StackPanel> + <StackPanel Spacing="10" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboCharacterLabel}" /> + <ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" ItemsSource="{Binding AmiiboList}" /> + </StackPanel> + </Grid> + <StackPanel Margin="20" Grid.Row="2"> + <Image Source="{Binding AmiiboImage}" Height="350" Width="350" HorizontalAlignment="Center" /> + <ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" + Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch"> + <TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center" + TextAlignment="Center" /> + </ScrollViewer> + </StackPanel> + <Grid Grid.Row="3"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding ShowAllAmiibo}" + Content="{locale:Locale AmiiboOptionsShowAllLabel}" /> + <CheckBox Margin="10" VerticalContentAlignment="Center" Grid.Column="1" IsChecked="{Binding UseRandomUuid}" + Content="{locale:Locale AmiiboOptionsUsRandomTagLabel}" /> + + <Button Grid.Column="3" IsEnabled="{Binding EnableScanning}" Width="80" + Content="{locale:Locale AmiiboScanButtonLabel}" Name="ScanButton" + Click="ScanButton_Click" /> + <Button Grid.Column="4" Margin="10,0" Width="80" Content="{locale:Locale InputDialogCancel}" + Name="CancelButton" + Click="CancelButton_Click" /> + </Grid> + </Grid> +</window:StyleableWindow> diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs new file mode 100644 index 00000000..8829cb10 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs @@ -0,0 +1,60 @@ +using Avalonia.Interactivity; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.Common.Models.Amiibo; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class AmiiboWindow : StyleableWindow + { + public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId) + { + ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId) + { + ShowAllAmiibo = showAll, + }; + + DataContext = ViewModel; + + InitializeComponent(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo]; + } + + public AmiiboWindow() + { + ViewModel = new AmiiboWindowViewModel(this, string.Empty, string.Empty); + + DataContext = ViewModel; + + InitializeComponent(); + + if (Program.PreviewerDetached) + { + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo]; + } + } + + public bool IsScanned { get; set; } + public AmiiboApi ScannedAmiibo { get; set; } + public AmiiboWindowViewModel ViewModel { get; set; } + + private void ScanButton_Click(object sender, RoutedEventArgs e) + { + if (ViewModel.AmiiboSelectedIndex > -1) + { + AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; + ScannedAmiibo = amiibo; + IsScanned = true; + Close(); + } + } + + private void CancelButton_Click(object sender, RoutedEventArgs e) + { + IsScanned = false; + + Close(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.cs deleted file mode 100644 index d8c0b0c0..00000000 --- a/src/Ryujinx/UI/Windows/AmiiboWindow.cs +++ /dev/null @@ -1,438 +0,0 @@ -using Gdk; -using Gtk; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Models.Amiibo; -using Ryujinx.UI.Widgets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Window = Gtk.Window; - -namespace Ryujinx.UI.Windows -{ - public partial class AmiiboWindow : Window - { - private const string DefaultJson = "{ \"amiibo\": [] }"; - - public string AmiiboId { get; private set; } - - public int DeviceId { get; set; } - public string TitleId { get; set; } - public string LastScannedAmiiboId { get; set; } - public bool LastScannedAmiiboShowAll { get; set; } - - public ResponseType Response { get; private set; } - - public bool UseRandomUuid - { - get - { - return _randomUuidCheckBox.Active; - } - } - - private readonly HttpClient _httpClient; - private readonly string _amiiboJsonPath; - - private readonly byte[] _amiiboLogoBytes; - - private List<AmiiboApi> _amiiboList; - - private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") - { - Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - InitializeComponent(); - - _httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(30), - }; - - Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); - - _amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); - _amiiboList = new List<AmiiboApi>(); - - _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); - _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); - - _scanButton.Sensitive = false; - _randomUuidCheckBox.Sensitive = false; - - _ = LoadContentAsync(); - } - - private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) - { - if (string.IsNullOrEmpty(json)) - { - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); - - return false; - } - - try - { - amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); - - return true; - } - catch (JsonException exception) - { - Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); - - return false; - } - } - - private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson() - { - bool localIsValid = false; - bool remoteIsValid = false; - AmiiboJson amiiboJson = new(); - - try - { - try - { - if (File.Exists(_amiiboJsonPath)) - { - localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); - } - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); - } - - if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) - { - remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); - } - } - catch (Exception exception) - { - if (!(localIsValid || remoteIsValid)) - { - Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); - - // Neither local or remote files are valid JSON, close window. - ShowInfoDialog(); - Close(); - } - else if (!remoteIsValid) - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); - - // Only the local file is valid, the local one should be used - // but the user should be warned. - ShowInfoDialog(); - } - } - - return amiiboJson; - } - - private async Task LoadContentAsync() - { - AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); - - _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); - - if (LastScannedAmiiboShowAll) - { - _showAllCheckBox.Click(); - } - - ParseAmiiboData(); - - _showAllCheckBox.Clicked += ShowAllCheckBox_Clicked; - } - - private void ParseAmiiboData() - { - List<string> comboxItemList = new(); - - for (int i = 0; i < _amiiboList.Count; i++) - { - if (!comboxItemList.Contains(_amiiboList[i].AmiiboSeries)) - { - if (!_showAllCheckBox.Active) - { - foreach (var game in _amiiboList[i].GamesSwitch) - { - if (game != null) - { - if (game.GameId.Contains(TitleId)) - { - comboxItemList.Add(_amiiboList[i].AmiiboSeries); - _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); - - break; - } - } - } - } - else - { - comboxItemList.Add(_amiiboList[i].AmiiboSeries); - _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); - } - } - } - - _amiiboSeriesComboBox.Changed += SeriesComboBox_Changed; - _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; - - if (LastScannedAmiiboId != "") - { - SelectLastScannedAmiibo(); - } - else - { - _amiiboSeriesComboBox.Active = 0; - } - } - - private void SelectLastScannedAmiibo() - { - bool isSet = _amiiboSeriesComboBox.SetActiveId(_amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == LastScannedAmiiboId).AmiiboSeries); - isSet = _amiiboCharsComboBox.SetActiveId(LastScannedAmiiboId); - - if (isSet == false) - { - _amiiboSeriesComboBox.Active = 0; - } - } - - private async Task<bool> NeedsUpdate(DateTime oldLastModified) - { - try - { - HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); - - if (response.IsSuccessStatusCode) - { - return response.Content.Headers.LastModified != oldLastModified; - } - } - catch (HttpRequestException exception) - { - Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); - } - - return false; - } - - private async Task<string> DownloadAmiiboJson() - { - try - { - HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - - if (response.IsSuccessStatusCode) - { - string amiiboJsonString = await response.Content.ReadAsStringAsync(); - - try - { - using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); - dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); - } - - return amiiboJsonString; - } - - Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); - } - catch (HttpRequestException exception) - { - Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); - } - - GtkDialog.CreateInfoDialog("Amiibo API", "An error occured while fetching information from the API."); - - return null; - } - - private async Task UpdateAmiiboPreview(string imageUrl) - { - HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); - - if (response.IsSuccessStatusCode) - { - byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); - Pixbuf amiiboPreview = new(amiiboPreviewBytes); - - float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width, - (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height); - - int resizeHeight = (int)(amiiboPreview.Height * ratio); - int resizeWidth = (int)(amiiboPreview.Width * ratio); - - _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, InterpType.Bilinear); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}"); - } - } - - private static void ShowInfoDialog() - { - GtkDialog.CreateInfoDialog("Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online."); - } - - // - // Events - // - private void SeriesComboBox_Changed(object sender, EventArgs args) - { - _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; - - _amiiboCharsComboBox.RemoveAll(); - - List<AmiiboApi> amiiboSortedList = _amiiboList.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeriesComboBox.ActiveId).OrderBy(amiibo => amiibo.Name).ToList(); - - List<string> comboxItemList = new(); - - for (int i = 0; i < amiiboSortedList.Count; i++) - { - if (!comboxItemList.Contains(amiiboSortedList[i].Head + amiiboSortedList[i].Tail)) - { - if (!_showAllCheckBox.Active) - { - foreach (var game in amiiboSortedList[i].GamesSwitch) - { - if (game != null) - { - if (game.GameId.Contains(TitleId)) - { - comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); - _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); - - break; - } - } - } - } - else - { - comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); - _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); - } - } - } - - _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; - - _amiiboCharsComboBox.Active = 0; - - _scanButton.Sensitive = true; - _randomUuidCheckBox.Sensitive = true; - } - - private void CharacterComboBox_Changed(object sender, EventArgs args) - { - AmiiboId = _amiiboCharsComboBox.ActiveId; - - _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); - - string imageUrl = _amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image; - - var usageStringBuilder = new StringBuilder(); - - for (int i = 0; i < _amiiboList.Count; i++) - { - if (_amiiboList[i].Head + _amiiboList[i].Tail == _amiiboCharsComboBox.ActiveId) - { - bool writable = false; - - foreach (var item in _amiiboList[i].GamesSwitch) - { - if (item.GameId.Contains(TitleId)) - { - foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) - { - usageStringBuilder.Append(Environment.NewLine); - usageStringBuilder.Append($"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); - - writable = usageItem.Write; - } - } - } - - if (usageStringBuilder.Length == 0) - { - usageStringBuilder.Append("Unknown."); - } - - _gameUsageLabel.Text = $"Usage{(writable ? " (Writable)" : "")} : {usageStringBuilder}"; - } - } - - _ = UpdateAmiiboPreview(imageUrl); - } - - private void ShowAllCheckBox_Clicked(object sender, EventArgs e) - { - _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); - - _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed; - _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; - - _amiiboSeriesComboBox.RemoveAll(); - _amiiboCharsComboBox.RemoveAll(); - - _scanButton.Sensitive = false; - _randomUuidCheckBox.Sensitive = false; - - new Task(ParseAmiiboData).Start(); - } - - private void ScanButton_Pressed(object sender, EventArgs args) - { - LastScannedAmiiboShowAll = _showAllCheckBox.Active; - - Response = ResponseType.Ok; - - Close(); - } - - private void CancelButton_Pressed(object sender, EventArgs args) - { - AmiiboId = ""; - LastScannedAmiiboId = ""; - LastScannedAmiiboShowAll = false; - - Response = ResponseType.Cancel; - - Close(); - } - - protected override void Dispose(bool disposing) - { - _httpClient.Dispose(); - - base.Dispose(disposing); - } - } -} diff --git a/src/Ryujinx/UI/Windows/AvatarWindow.cs b/src/Ryujinx/UI/Windows/AvatarWindow.cs deleted file mode 100644 index 7cddc362..00000000 --- a/src/Ryujinx/UI/Windows/AvatarWindow.cs +++ /dev/null @@ -1,291 +0,0 @@ -using Gtk; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Ncm; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Common.Memory; -using Ryujinx.HLE.FileSystem; -using Ryujinx.UI.Common.Configuration; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Image = SixLabors.ImageSharp.Image; - -namespace Ryujinx.UI.Windows -{ - public class AvatarWindow : Window - { - public byte[] SelectedProfileImage; - public bool NewUser; - - private static readonly Dictionary<string, byte[]> _avatarDict = new(); - - private readonly ListStore _listStore; - private readonly IconView _iconView; - private readonly Button _setBackgroungColorButton; - private Gdk.RGBA _backgroundColor; - - public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar") - { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - CanFocus = false; - Resizable = false; - Modal = true; - TypeHint = Gdk.WindowTypeHint.Dialog; - - SetDefaultSize(740, 400); - SetPosition(WindowPosition.Center); - - Box vbox = new(Orientation.Vertical, 0); - Add(vbox); - - ScrolledWindow scrolledWindow = new() - { - ShadowType = ShadowType.EtchedIn, - }; - scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic); - - Box hbox = new(Orientation.Horizontal, 0); - - Button chooseButton = new() - { - Label = "Choose", - CanFocus = true, - ReceivesDefault = true, - }; - chooseButton.Clicked += ChooseButton_Pressed; - - _setBackgroungColorButton = new Button() - { - Label = "Set Background Color", - CanFocus = true, - }; - _setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed; - - _backgroundColor.Red = 1; - _backgroundColor.Green = 1; - _backgroundColor.Blue = 1; - _backgroundColor.Alpha = 1; - - Button closeButton = new() - { - Label = "Close", - CanFocus = true, - }; - closeButton.Clicked += CloseButton_Pressed; - - vbox.PackStart(scrolledWindow, true, true, 0); - hbox.PackStart(chooseButton, true, true, 0); - hbox.PackStart(_setBackgroungColorButton, true, true, 0); - hbox.PackStart(closeButton, true, true, 0); - vbox.PackStart(hbox, false, false, 0); - - _listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf)); - _listStore.SetSortColumnId(0, SortType.Ascending); - - _iconView = new IconView(_listStore) - { - ItemWidth = 64, - ItemPadding = 10, - PixbufColumn = 1, - }; - - _iconView.SelectionChanged += IconView_SelectionChanged; - - scrolledWindow.Add(_iconView); - - _iconView.GrabFocus(); - - ProcessAvatars(); - - ShowAll(); - } - - public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) - { - if (_avatarDict.Count > 0) - { - return; - } - - string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); - string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - - if (!string.IsNullOrWhiteSpace(avatarPath)) - { - using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); - - Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); - IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - foreach (var item in romfs.EnumerateEntries()) - { - // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. - - if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) - { - using var file = new UniqueRef<IFile>(); - - romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); - using MemoryStream streamPng = MemoryStreamManager.Shared.GetStream(); - file.Get.AsStream().CopyTo(stream); - - stream.Position = 0; - - Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); - - avatarImage.SaveAsPng(streamPng); - - _avatarDict.Add(item.FullPath, streamPng.ToArray()); - } - } - } - } - - private void ProcessAvatars() - { - _listStore.Clear(); - - foreach (var avatar in _avatarDict) - { - _listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96)); - } - - _iconView.SelectPath(new TreePath(new[] { 0 })); - } - - private byte[] ProcessImage(byte[] data) - { - using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); - - Image avatarImage = Image.Load(data, new PngDecoder()); - - avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( - (byte)(_backgroundColor.Red * 255), - (byte)(_backgroundColor.Green * 255), - (byte)(_backgroundColor.Blue * 255), - (byte)(_backgroundColor.Alpha * 255) - ))); - avatarImage.SaveAsJpeg(streamJpg); - - return streamJpg.ToArray(); - } - - private void CloseButton_Pressed(object sender, EventArgs e) - { - SelectedProfileImage = null; - - Close(); - } - - private void IconView_SelectionChanged(object sender, EventArgs e) - { - if (_iconView.SelectedItems.Length > 0) - { - _listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]); - - SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]); - } - } - - private void SetBackgroungColorButton_Pressed(object sender, EventArgs e) - { - using ColorChooserDialog colorChooserDialog = new("Set Background Color", this); - - colorChooserDialog.UseAlpha = false; - colorChooserDialog.Rgba = _backgroundColor; - - if (colorChooserDialog.Run() == (int)ResponseType.Ok) - { - _backgroundColor = colorChooserDialog.Rgba; - - ProcessAvatars(); - } - - colorChooserDialog.Hide(); - } - - private void ChooseButton_Pressed(object sender, EventArgs e) - { - Close(); - } - - private static byte[] DecompressYaz0(Stream stream) - { - using BinaryReader reader = new(stream); - - reader.ReadInt32(); // Magic - - uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); - - reader.ReadInt64(); // Padding - - byte[] input = new byte[stream.Length - stream.Position]; - stream.Read(input, 0, input.Length); - - long inputOffset = 0; - - byte[] output = new byte[decodedLength]; - long outputOffset = 0; - - ushort mask = 0; - byte header = 0; - - while (outputOffset < decodedLength) - { - if ((mask >>= 1) == 0) - { - header = input[inputOffset++]; - mask = 0x80; - } - - if ((header & mask) > 0) - { - if (outputOffset == output.Length) - { - break; - } - - output[outputOffset++] = input[inputOffset++]; - } - else - { - byte byte1 = input[inputOffset++]; - byte byte2 = input[inputOffset++]; - - int dist = ((byte1 & 0xF) << 8) | byte2; - int position = (int)outputOffset - (dist + 1); - - int length = byte1 >> 4; - if (length == 0) - { - length = input[inputOffset++] + 0x12; - } - else - { - length += 2; - } - - while (length-- > 0) - { - output[outputOffset++] = output[position++]; - } - } - } - - return output; - } - } -} diff --git a/src/Ryujinx/UI/Windows/CheatWindow.axaml b/src/Ryujinx/UI/Windows/CheatWindow.axaml new file mode 100644 index 00000000..57d5f7ef --- /dev/null +++ b/src/Ryujinx/UI/Windows/CheatWindow.axaml @@ -0,0 +1,126 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.UI.Windows.CheatWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:model="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" + Width="500" + Height="500" + MinWidth="500" + MinHeight="500" + x:DataType="window:CheatWindow" + WindowStartupLocation="CenterOwner" + mc:Ignorable="d" + Focusable="True"> + <Window.Styles> + <Style Selector="TreeViewItem"> + <Setter Property="IsExpanded" Value="True" /> + </Style> + </Window.Styles> + <Grid Name="CheatGrid" Margin="15"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <TextBlock + Grid.Row="1" + Grid.Column="0" + Grid.ColumnSpan="2" + MaxWidth="500" + Margin="20,15,20,5" + HorizontalAlignment="Center" + VerticalAlignment="Center" + LineHeight="18" + Text="{Binding Heading}" + TextAlignment="Center" + TextWrapping="Wrap" /> + <TextBlock + Grid.Row="2" + Grid.Column="0" + MaxWidth="500" + Margin="140,15,20,5" + HorizontalAlignment="Center" + VerticalAlignment="Center" + LineHeight="30" + Text="{locale:Locale BuildId}" + TextAlignment="Center" + TextWrapping="Wrap" /> + <TextBox + Grid.Row="2" + Grid.Column="1" + Margin="0,5,110,5" + MinWidth="160" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding BuildId}" + IsReadOnly="True" /> + <Border + Grid.Row="3" + Grid.Column="0" + Grid.ColumnSpan="2" + Margin="5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="Gray" + BorderThickness="1"> + <TreeView + Name="CheatsView" + MinHeight="300" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + ItemsSource="{Binding LoadedCheats}"> + <TreeView.Styles> + <Styles> + <Style Selector="TreeViewItem:empty /template/ ItemsPresenter"> + <Setter Property="IsVisible" Value="False" /> + </Style> + </Styles> + </TreeView.Styles> + <TreeView.ItemTemplate> + <TreeDataTemplate ItemsSource="{Binding SubNodes}"> + <StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> + <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" /> + <TextBlock Width="150" Text="{Binding CleanName}" IsVisible="{Binding !IsRootNode}" /> + <TextBlock Width="150" Text="{Binding BuildId}" IsVisible="{Binding IsRootNode}" /> + <TextBlock Text="{Binding Path}" IsVisible="{Binding IsRootNode}" /> + </StackPanel> + </TreeDataTemplate> + </TreeView.ItemTemplate> + </TreeView> + </Border> + <DockPanel + Grid.Row="4" + Grid.Column="0" + Grid.ColumnSpan="2" + Margin="0" + HorizontalAlignment="Stretch"> + <DockPanel Margin="0" HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + Command="{Binding Save}" + IsVisible="{Binding !NoCheatsFound}"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Command="{Binding Close}"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </DockPanel> + </DockPanel> + </Grid> +</window:StyleableWindow> diff --git a/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs new file mode 100644 index 00000000..d78e48a4 --- /dev/null +++ b/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs @@ -0,0 +1,123 @@ +using Avalonia.Collections; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class CheatWindow : StyleableWindow + { + private readonly string _enabledCheatsPath; + public bool NoCheatsFound { get; } + + public AvaloniaList<CheatNode> LoadedCheats { get; } + + public string Heading { get; } + public string BuildId { get; } + + public CheatWindow() + { + DataContext = this; + + InitializeComponent(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; + } + + public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath) + { + LoadedCheats = new AvaloniaList<CheatNode>(); + + Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper()); + BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath); + + InitializeComponent(); + + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId); + ulong titleIdValue = ulong.Parse(titleId, NumberStyles.HexNumber); + + _enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + string[] enabled = Array.Empty<string>(); + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + + CheatNode currentGroup = null; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + string parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); + currentGroup = new CheatNode("", buildId, parentPath, true); + + LoadedCheats.Add(currentGroup); + } + + var model = new CheatNode(cheat.Name, buildId, "", false, enabled.Contains($"{buildId}-{cheat.Name}")); + currentGroup?.SubNodes.Add(model); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + NoCheatsFound = true; + } + + DataContext = this; + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; + } + + public void Save() + { + if (NoCheatsFound) + { + return; + } + + List<string> enabledCheats = new(); + + foreach (var cheats in LoadedCheats) + { + foreach (var cheat in cheats.SubNodes) + { + if (cheat.IsEnabled) + { + enabledCheats.Add(cheat.BuildIdKey); + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Close(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/CheatWindow.cs b/src/Ryujinx/UI/Windows/CheatWindow.cs deleted file mode 100644 index 73ee870c..00000000 --- a/src/Ryujinx/UI/Windows/CheatWindow.cs +++ /dev/null @@ -1,156 +0,0 @@ -using Gtk; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS; -using Ryujinx.UI.App.Common; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using GUI = Gtk.Builder.ObjectAttribute; - -namespace Ryujinx.UI.Windows -{ - public class CheatWindow : Window - { - private readonly string _enabledCheatsPath; - private readonly bool _noCheatsFound; - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] Label _baseTitleInfoLabel; - [GUI] TextView _buildIdTextView; - [GUI] TreeView _cheatTreeView; - [GUI] Button _saveButton; -#pragma warning restore CS0649, IDE0044 - - public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } - - private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) - { - builder.Autoconnect(this); - _baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]"; - _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}"; - - string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16")); - - _enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt"); - - _cheatTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string), typeof(string)); - - CellRendererToggle enableToggle = new(); - enableToggle.Toggled += (sender, args) => - { - _cheatTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); - bool newValue = !(bool)_cheatTreeView.Model.GetValue(treeIter, 0); - _cheatTreeView.Model.SetValue(treeIter, 0, newValue); - - if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) - { - do - { - _cheatTreeView.Model.SetValue(childIter, 0, newValue); - } - while (_cheatTreeView.Model.IterNext(ref childIter)); - } - }; - - _cheatTreeView.AppendColumn("Enabled", enableToggle, "active", 0); - _cheatTreeView.AppendColumn("Name", new CellRendererText(), "text", 1); - _cheatTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); - - var buildIdColumn = _cheatTreeView.AppendColumn("Build Id", new CellRendererText(), "text", 3); - buildIdColumn.Visible = false; - - string[] enabled = Array.Empty<string>(); - - if (File.Exists(_enabledCheatsPath)) - { - enabled = File.ReadAllLines(_enabledCheatsPath); - } - - int cheatAdded = 0; - - var mods = new ModLoader.ModCache(); - - ModLoader.QueryContentsDir(mods, new DirectoryInfo(System.IO.Path.Combine(modsBasePath, "contents")), titleId); - - string currentCheatFile = string.Empty; - string buildId = string.Empty; - TreeIter parentIter = default; - - foreach (var cheat in mods.Cheats) - { - if (cheat.Path.FullName != currentCheatFile) - { - currentCheatFile = cheat.Path.FullName; - string parentPath = currentCheatFile.Replace(titleModsPath, ""); - - buildId = System.IO.Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); - parentIter = ((TreeStore)_cheatTreeView.Model).AppendValues(false, buildId, parentPath, ""); - } - - string cleanName = cheat.Name[1..^7]; - ((TreeStore)_cheatTreeView.Model).AppendValues(parentIter, enabled.Contains($"{buildId}-{cheat.Name}"), cleanName, "", buildId); - - cheatAdded++; - } - - if (cheatAdded == 0) - { - ((TreeStore)_cheatTreeView.Model).AppendValues(false, "No Cheats Found", "", ""); - _cheatTreeView.GetColumn(0).Visible = false; - - _noCheatsFound = true; - - _saveButton.Visible = false; - } - - _cheatTreeView.ExpandAll(); - } - - private void SaveButton_Clicked(object sender, EventArgs args) - { - if (_noCheatsFound) - { - return; - } - - List<string> enabledCheats = new(); - - if (_cheatTreeView.Model.GetIterFirst(out TreeIter parentIter)) - { - do - { - if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) - { - do - { - var enabled = (bool)_cheatTreeView.Model.GetValue(childIter, 0); - - if (enabled) - { - var name = _cheatTreeView.Model.GetValue(childIter, 1).ToString(); - var buildId = _cheatTreeView.Model.GetValue(childIter, 3).ToString(); - - enabledCheats.Add($"{buildId}-<{name} Cheat>"); - } - } - while (_cheatTreeView.Model.IterNext(ref childIter)); - } - } - while (_cheatTreeView.Model.IterNext(ref parentIter)); - } - - Directory.CreateDirectory(System.IO.Path.GetDirectoryName(_enabledCheatsPath)); - - File.WriteAllLines(_enabledCheatsPath, enabledCheats); - - Dispose(); - } - - private void CancelButton_Clicked(object sender, EventArgs args) - { - Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/CheatWindow.glade b/src/Ryujinx/UI/Windows/CheatWindow.glade deleted file mode 100644 index 9a165f1a..00000000 --- a/src/Ryujinx/UI/Windows/CheatWindow.glade +++ /dev/null @@ -1,150 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.21.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkWindow" id="_cheatWindow"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - Cheat Manager</property> - <property name="default_width">440</property> - <property name="default_height">550</property> - <child> - <object class="GtkBox" id="MainBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CheatBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel" id="_baseTitleInfoLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="label" translatable="yes">Available Cheats</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkTextView" id="_buildIdTextView"> - <property name="visible">True</property> - <property name="margin_top">10</property> - <property name="halign">center</property> - <property name="margin_bottom">10</property> - <property name="editable">False</property> - <property name="cursor_visible">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkTreeView" id="_cheatTreeView"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"/> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="_saveButton"> - <property name="label" translatable="yes">Save</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_cancelButton"> - <property name="label" translatable="yes">Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <child type="titlebar"> - <placeholder/> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml new file mode 100644 index 00000000..8b52bade --- /dev/null +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml @@ -0,0 +1,25 @@ +<window:StyleableWindow + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" + mc:Ignorable="d" + d:DesignWidth="800" + d:DesignHeight="450" + x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow" + Title="ContentDialogOverlayWindow" + Focusable="False"> + <window:StyleableWindow.Styles> + <Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot"> + <Setter Property="Background" + Value="Transparent" /> + </Style> + </window:StyleableWindow.Styles> + <ui:ContentDialog Name="ContentDialog" + IsPrimaryButtonEnabled="True" + IsSecondaryButtonEnabled="True" + IsVisible="False" + Focusable="True"/> +</window:StyleableWindow> diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs new file mode 100644 index 00000000..2b12d72f --- /dev/null +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs @@ -0,0 +1,21 @@ +using Avalonia.Controls; +using Avalonia.Media; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class ContentDialogOverlayWindow : StyleableWindow + { + public ContentDialogOverlayWindow() + { + InitializeComponent(); + + ExtendClientAreaToDecorationsHint = true; + TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; + WindowStartupLocation = WindowStartupLocation.Manual; + SystemDecorations = SystemDecorations.None; + ExtendClientAreaTitleBarHeightHint = 0; + Background = Brushes.Transparent; + CanResize = false; + } + } +} diff --git a/src/Ryujinx/UI/Windows/ControllerWindow.cs b/src/Ryujinx/UI/Windows/ControllerWindow.cs deleted file mode 100644 index 95411344..00000000 --- a/src/Ryujinx/UI/Windows/ControllerWindow.cs +++ /dev/null @@ -1,1230 +0,0 @@ -using Gtk; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.Input; -using Ryujinx.Input.Assigner; -using Ryujinx.Input.GTK3; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Widgets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text.Json; -using System.Threading; -using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; -using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; -using GUI = Gtk.Builder.ObjectAttribute; -using Key = Ryujinx.Common.Configuration.Hid.Key; - -namespace Ryujinx.UI.Windows -{ - public class ControllerWindow : Window - { - private readonly PlayerIndex _playerIndex; - private readonly InputConfig _inputConfig; - - private bool _isWaitingForInput; - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] Adjustment _controllerStrongRumble; - [GUI] Adjustment _controllerWeakRumble; - [GUI] Adjustment _controllerDeadzoneLeft; - [GUI] Adjustment _controllerDeadzoneRight; - [GUI] Adjustment _controllerRangeLeft; - [GUI] Adjustment _controllerRangeRight; - [GUI] Adjustment _controllerTriggerThreshold; - [GUI] Adjustment _slotNumber; - [GUI] Adjustment _altSlotNumber; - [GUI] Adjustment _sensitivity; - [GUI] Adjustment _gyroDeadzone; - [GUI] CheckButton _enableMotion; - [GUI] CheckButton _enableCemuHook; - [GUI] CheckButton _mirrorInput; - [GUI] Entry _dsuServerHost; - [GUI] Entry _dsuServerPort; - [GUI] ComboBoxText _inputDevice; - [GUI] ComboBoxText _profile; - [GUI] Box _settingsBox; - [GUI] Box _motionAltBox; - [GUI] Box _motionBox; - [GUI] Box _dsuServerHostBox; - [GUI] Box _dsuServerPortBox; - [GUI] Box _motionControllerSlot; - [GUI] Grid _leftStickKeyboard; - [GUI] Grid _leftStickController; - [GUI] Box _deadZoneLeftBox; - [GUI] Box _rangeLeftBox; - [GUI] Grid _rightStickKeyboard; - [GUI] Grid _rightStickController; - [GUI] Box _deadZoneRightBox; - [GUI] Box _rangeRightBox; - [GUI] Grid _leftSideTriggerBox; - [GUI] Grid _rightSideTriggerBox; - [GUI] Box _triggerThresholdBox; - [GUI] ComboBoxText _controllerType; - [GUI] ToggleButton _lStick; - [GUI] CheckButton _invertLStickX; - [GUI] CheckButton _invertLStickY; - [GUI] CheckButton _rotateL90CW; - [GUI] ToggleButton _lStickUp; - [GUI] ToggleButton _lStickDown; - [GUI] ToggleButton _lStickLeft; - [GUI] ToggleButton _lStickRight; - [GUI] ToggleButton _lStickButton; - [GUI] ToggleButton _dpadUp; - [GUI] ToggleButton _dpadDown; - [GUI] ToggleButton _dpadLeft; - [GUI] ToggleButton _dpadRight; - [GUI] ToggleButton _minus; - [GUI] ToggleButton _l; - [GUI] ToggleButton _zL; - [GUI] ToggleButton _rStick; - [GUI] CheckButton _invertRStickX; - [GUI] CheckButton _invertRStickY; - [GUI] CheckButton _rotateR90CW; - [GUI] ToggleButton _rStickUp; - [GUI] ToggleButton _rStickDown; - [GUI] ToggleButton _rStickLeft; - [GUI] ToggleButton _rStickRight; - [GUI] ToggleButton _rStickButton; - [GUI] ToggleButton _a; - [GUI] ToggleButton _b; - [GUI] ToggleButton _x; - [GUI] ToggleButton _y; - [GUI] ToggleButton _plus; - [GUI] ToggleButton _r; - [GUI] ToggleButton _zR; - [GUI] ToggleButton _lSl; - [GUI] ToggleButton _lSr; - [GUI] ToggleButton _rSl; - [GUI] ToggleButton _rSr; - [GUI] Image _controllerImage; - [GUI] CheckButton _enableRumble; - [GUI] Box _rumbleBox; -#pragma warning restore CS0649, IDE0044 - - private readonly MainWindow _mainWindow; - private readonly IGamepadDriver _gtk3KeyboardDriver; - private IGamepad _selectedGamepad; - private bool _mousePressed; - private bool _middleMousePressed; - - private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.UI.Windows.ControllerWindow.glade"), controllerId) { } - - private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) - { - _mainWindow = mainWindow; - _selectedGamepad = null; - - // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... - _gtk3KeyboardDriver = new GTK3KeyboardDriver(this); - - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - builder.Autoconnect(this); - - _playerIndex = controllerId; - _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); - - Title = $"Ryujinx - Controller Settings - {_playerIndex}"; - - if (_playerIndex == PlayerIndex.Handheld) - { - _controllerType.Append(ControllerType.Handheld.ToString(), "Handheld"); - _controllerType.Sensitive = false; - } - else - { - _controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller"); - _controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair"); - _controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left"); - _controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right"); - } - - _controllerType.Active = 0; // Set initial value to first in list. - - // Bind Events. - _lStick.Clicked += ButtonForStick_Pressed; - _lStickUp.Clicked += Button_Pressed; - _lStickDown.Clicked += Button_Pressed; - _lStickLeft.Clicked += Button_Pressed; - _lStickRight.Clicked += Button_Pressed; - _lStickButton.Clicked += Button_Pressed; - _dpadUp.Clicked += Button_Pressed; - _dpadDown.Clicked += Button_Pressed; - _dpadLeft.Clicked += Button_Pressed; - _dpadRight.Clicked += Button_Pressed; - _minus.Clicked += Button_Pressed; - _l.Clicked += Button_Pressed; - _zL.Clicked += Button_Pressed; - _lSl.Clicked += Button_Pressed; - _lSr.Clicked += Button_Pressed; - _rStick.Clicked += ButtonForStick_Pressed; - _rStickUp.Clicked += Button_Pressed; - _rStickDown.Clicked += Button_Pressed; - _rStickLeft.Clicked += Button_Pressed; - _rStickRight.Clicked += Button_Pressed; - _rStickButton.Clicked += Button_Pressed; - _a.Clicked += Button_Pressed; - _b.Clicked += Button_Pressed; - _x.Clicked += Button_Pressed; - _y.Clicked += Button_Pressed; - _plus.Clicked += Button_Pressed; - _r.Clicked += Button_Pressed; - _zR.Clicked += Button_Pressed; - _rSl.Clicked += Button_Pressed; - _rSr.Clicked += Button_Pressed; - _enableCemuHook.Clicked += CemuHookCheckButtonPressed; - - // Setup current values. - UpdateInputDeviceList(); - SetAvailableOptions(); - - ClearValues(); - if (_inputDevice.ActiveId != null) - { - SetCurrentValues(); - } - - mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; - mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; - - _mainWindow.RendererWidget?.NpadManager.BlockInputUpdates(); - } - - private void CemuHookCheckButtonPressed(object sender, EventArgs e) - { - UpdateCemuHookSpecificFieldsVisibility(); - } - - private void HandleOnGamepadDisconnected(string id) - { - Application.Invoke(delegate - { - UpdateInputDeviceList(); - }); - } - - private void HandleOnGamepadConnected(string id) - { - Application.Invoke(delegate - { - UpdateInputDeviceList(); - }); - } - - protected override void OnDestroyed() - { - _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; - _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - - _mainWindow.RendererWidget?.NpadManager.UnblockInputUpdates(); - - _selectedGamepad?.Dispose(); - - _gtk3KeyboardDriver.Dispose(); - } - - private static string GetShortGamepadName(string str) - { - const string ShrinkChars = "..."; - const int MaxSize = 50; - - if (str.Length > MaxSize) - { - return $"{str.AsSpan(0, MaxSize - ShrinkChars.Length)}{ShrinkChars}"; - } - - return str; - } - - private void UpdateInputDeviceList() - { - _inputDevice.RemoveAll(); - _inputDevice.Append("disabled", "Disabled"); - _inputDevice.SetActiveId("disabled"); - - foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) - { - IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - - if (gamepad != null) - { - _inputDevice.Append($"keyboard/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); - - gamepad.Dispose(); - } - } - - foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) - { - IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - - if (gamepad != null) - { - _inputDevice.Append($"controller/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); - - gamepad.Dispose(); - } - } - - switch (_inputConfig) - { - case StandardKeyboardInputConfig keyboard: - _inputDevice.SetActiveId($"keyboard/{keyboard.Id}"); - break; - case StandardControllerInputConfig controller: - _inputDevice.SetActiveId($"controller/{controller.Id}"); - break; - } - } - - private void UpdateCemuHookSpecificFieldsVisibility() - { - if (_enableCemuHook.Active) - { - _dsuServerHostBox.Show(); - _dsuServerPortBox.Show(); - _motionControllerSlot.Show(); - _motionAltBox.Show(); - _mirrorInput.Show(); - } - else - { - _dsuServerHostBox.Hide(); - _dsuServerPortBox.Hide(); - _motionControllerSlot.Hide(); - _motionAltBox.Hide(); - _mirrorInput.Hide(); - } - } - - private void SetAvailableOptions() - { - if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) - { - ShowAll(); - _leftStickController.Hide(); - _rightStickController.Hide(); - _deadZoneLeftBox.Hide(); - _deadZoneRightBox.Hide(); - _rangeLeftBox.Hide(); - _rangeRightBox.Hide(); - _triggerThresholdBox.Hide(); - _motionBox.Hide(); - _rumbleBox.Hide(); - } - else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) - { - ShowAll(); - _leftStickKeyboard.Hide(); - _rightStickKeyboard.Hide(); - - UpdateCemuHookSpecificFieldsVisibility(); - } - else - { - _settingsBox.Hide(); - } - - ClearValues(); - } - - private void SetCurrentValues() - { - SetControllerSpecificFields(); - - SetProfiles(); - - if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig) - { - SetValues(_inputConfig); - } - else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig) - { - SetValues(_inputConfig); - } - } - - private void SetControllerSpecificFields() - { - _leftSideTriggerBox.Hide(); - _rightSideTriggerBox.Hide(); - _motionAltBox.Hide(); - - switch (_controllerType.ActiveId) - { - case "JoyconLeft": - _leftSideTriggerBox.Show(); - break; - case "JoyconRight": - _rightSideTriggerBox.Show(); - break; - case "JoyconPair": - _motionAltBox.Show(); - break; - } - - if (!OperatingSystem.IsMacOS()) - { - _controllerImage.Pixbuf = _controllerType.ActiveId switch - { - "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400), - "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500), - "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500), - _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500), - }; - } - } - - private void ClearValues() - { - _lStick.Label = "Unbound"; - _lStickUp.Label = "Unbound"; - _lStickDown.Label = "Unbound"; - _lStickLeft.Label = "Unbound"; - _lStickRight.Label = "Unbound"; - _lStickButton.Label = "Unbound"; - _dpadUp.Label = "Unbound"; - _dpadDown.Label = "Unbound"; - _dpadLeft.Label = "Unbound"; - _dpadRight.Label = "Unbound"; - _minus.Label = "Unbound"; - _l.Label = "Unbound"; - _zL.Label = "Unbound"; - _lSl.Label = "Unbound"; - _lSr.Label = "Unbound"; - _rStick.Label = "Unbound"; - _rStickUp.Label = "Unbound"; - _rStickDown.Label = "Unbound"; - _rStickLeft.Label = "Unbound"; - _rStickRight.Label = "Unbound"; - _rStickButton.Label = "Unbound"; - _a.Label = "Unbound"; - _b.Label = "Unbound"; - _x.Label = "Unbound"; - _y.Label = "Unbound"; - _plus.Label = "Unbound"; - _r.Label = "Unbound"; - _zR.Label = "Unbound"; - _rSl.Label = "Unbound"; - _rSr.Label = "Unbound"; - _controllerStrongRumble.Value = 1; - _controllerWeakRumble.Value = 1; - _controllerDeadzoneLeft.Value = 0; - _controllerDeadzoneRight.Value = 0; - _controllerRangeLeft.Value = 1; - _controllerRangeRight.Value = 1; - _controllerTriggerThreshold.Value = 0; - _mirrorInput.Active = false; - _enableMotion.Active = false; - _enableCemuHook.Active = false; - _slotNumber.Value = 0; - _altSlotNumber.Value = 0; - _sensitivity.Value = 100; - _gyroDeadzone.Value = 1; - _dsuServerHost.Buffer.Text = ""; - _dsuServerPort.Buffer.Text = ""; - _enableRumble.Active = false; - } - - private void SetValues(InputConfig config) - { - switch (config) - { - case StandardKeyboardInputConfig keyboardConfig: - if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString())) - { - _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld - ? ControllerType.Handheld.ToString() - : ControllerType.ProController.ToString()); - } - - _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString(); - _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString(); - _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString(); - _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString(); - _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString(); - _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString(); - _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString(); - _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString(); - _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString(); - _minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString(); - _l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString(); - _zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString(); - _lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString(); - _lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString(); - _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString(); - _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString(); - _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString(); - _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString(); - _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString(); - _a.Label = keyboardConfig.RightJoycon.ButtonA.ToString(); - _b.Label = keyboardConfig.RightJoycon.ButtonB.ToString(); - _x.Label = keyboardConfig.RightJoycon.ButtonX.ToString(); - _y.Label = keyboardConfig.RightJoycon.ButtonY.ToString(); - _plus.Label = keyboardConfig.RightJoycon.ButtonPlus.ToString(); - _r.Label = keyboardConfig.RightJoycon.ButtonR.ToString(); - _zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString(); - _rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString(); - _rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString(); - break; - - case StandardControllerInputConfig controllerConfig: - if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString())) - { - _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld - ? ControllerType.Handheld.ToString() - : ControllerType.ProController.ToString()); - } - - _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString(); - _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX; - _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY; - _rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW; - _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString(); - _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString(); - _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString(); - _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString(); - _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString(); - _minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString(); - _l.Label = controllerConfig.LeftJoycon.ButtonL.ToString(); - _zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString(); - _lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString(); - _lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString(); - _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString(); - _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX; - _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY; - _rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW; - _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString(); - _a.Label = controllerConfig.RightJoycon.ButtonA.ToString(); - _b.Label = controllerConfig.RightJoycon.ButtonB.ToString(); - _x.Label = controllerConfig.RightJoycon.ButtonX.ToString(); - _y.Label = controllerConfig.RightJoycon.ButtonY.ToString(); - _plus.Label = controllerConfig.RightJoycon.ButtonPlus.ToString(); - _r.Label = controllerConfig.RightJoycon.ButtonR.ToString(); - _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString(); - _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString(); - _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString(); - _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble; - _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble; - _enableRumble.Active = controllerConfig.Rumble.EnableRumble; - _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft; - _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight; - _controllerRangeLeft.Value = controllerConfig.RangeLeft; - _controllerRangeRight.Value = controllerConfig.RangeRight; - _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold; - _sensitivity.Value = controllerConfig.Motion.Sensitivity; - _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone; - _enableMotion.Active = controllerConfig.Motion.EnableMotion; - _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook; - - // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. - if (_controllerRangeLeft.Value <= 0.0 && _controllerRangeRight.Value <= 0.0) - { - _controllerRangeLeft.Value = 1.0; - _controllerRangeRight.Value = 1.0; - - Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); - } - - if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig) - { - _slotNumber.Value = cemuHookMotionConfig.Slot; - _altSlotNumber.Value = cemuHookMotionConfig.AltSlot; - _mirrorInput.Active = cemuHookMotionConfig.MirrorInput; - _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost; - _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString(); - } - - break; - } - } - - private InputConfig GetValues() - { - if (_inputDevice.ActiveId.StartsWith("keyboard")) - { -#pragma warning disable CA1806, IDE0055 // Disable formatting - Enum.TryParse(_lStickUp.Label, out Key lStickUp); - Enum.TryParse(_lStickDown.Label, out Key lStickDown); - Enum.TryParse(_lStickLeft.Label, out Key lStickLeft); - Enum.TryParse(_lStickRight.Label, out Key lStickRight); - Enum.TryParse(_lStickButton.Label, out Key lStickButton); - Enum.TryParse(_dpadUp.Label, out Key lDPadUp); - Enum.TryParse(_dpadDown.Label, out Key lDPadDown); - Enum.TryParse(_dpadLeft.Label, out Key lDPadLeft); - Enum.TryParse(_dpadRight.Label, out Key lDPadRight); - Enum.TryParse(_minus.Label, out Key lButtonMinus); - Enum.TryParse(_l.Label, out Key lButtonL); - Enum.TryParse(_zL.Label, out Key lButtonZl); - Enum.TryParse(_lSl.Label, out Key lButtonSl); - Enum.TryParse(_lSr.Label, out Key lButtonSr); - - Enum.TryParse(_rStickUp.Label, out Key rStickUp); - Enum.TryParse(_rStickDown.Label, out Key rStickDown); - Enum.TryParse(_rStickLeft.Label, out Key rStickLeft); - Enum.TryParse(_rStickRight.Label, out Key rStickRight); - Enum.TryParse(_rStickButton.Label, out Key rStickButton); - Enum.TryParse(_a.Label, out Key rButtonA); - Enum.TryParse(_b.Label, out Key rButtonB); - Enum.TryParse(_x.Label, out Key rButtonX); - Enum.TryParse(_y.Label, out Key rButtonY); - Enum.TryParse(_plus.Label, out Key rButtonPlus); - Enum.TryParse(_r.Label, out Key rButtonR); - Enum.TryParse(_zR.Label, out Key rButtonZr); - Enum.TryParse(_rSl.Label, out Key rButtonSl); - Enum.TryParse(_rSr.Label, out Key rButtonSr); -#pragma warning restore CA1806, IDE0055 - - return new StandardKeyboardInputConfig - { - Backend = InputBackendType.WindowKeyboard, - Version = InputConfig.CurrentVersion, - Id = _inputDevice.ActiveId.Split("/")[1], - ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId), - PlayerIndex = _playerIndex, - LeftJoycon = new LeftJoyconCommonConfig<Key> - { - ButtonMinus = lButtonMinus, - ButtonL = lButtonL, - ButtonZl = lButtonZl, - ButtonSl = lButtonSl, - ButtonSr = lButtonSr, - DpadUp = lDPadUp, - DpadDown = lDPadDown, - DpadLeft = lDPadLeft, - DpadRight = lDPadRight, - }, - LeftJoyconStick = new JoyconConfigKeyboardStick<Key> - { - StickUp = lStickUp, - StickDown = lStickDown, - StickLeft = lStickLeft, - StickRight = lStickRight, - StickButton = lStickButton, - }, - RightJoycon = new RightJoyconCommonConfig<Key> - { - ButtonA = rButtonA, - ButtonB = rButtonB, - ButtonX = rButtonX, - ButtonY = rButtonY, - ButtonPlus = rButtonPlus, - ButtonR = rButtonR, - ButtonZr = rButtonZr, - ButtonSl = rButtonSl, - ButtonSr = rButtonSr, - }, - RightJoyconStick = new JoyconConfigKeyboardStick<Key> - { - StickUp = rStickUp, - StickDown = rStickDown, - StickLeft = rStickLeft, - StickRight = rStickRight, - StickButton = rStickButton, - }, - }; - } - - if (_inputDevice.ActiveId.StartsWith("controller")) - { -#pragma warning disable CA1806, IDE0055 // Disable formatting - Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick); - Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton); - Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus); - Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL); - Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl); - Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl); - Enum.TryParse(_lSr.Label, out ConfigGamepadInputId lButtonSr); - Enum.TryParse(_dpadUp.Label, out ConfigGamepadInputId lDPadUp); - Enum.TryParse(_dpadDown.Label, out ConfigGamepadInputId lDPadDown); - Enum.TryParse(_dpadLeft.Label, out ConfigGamepadInputId lDPadLeft); - Enum.TryParse(_dpadRight.Label, out ConfigGamepadInputId lDPadRight); - - Enum.TryParse(_rStick.Label, out ConfigStickInputId rStick); - Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton); - Enum.TryParse(_a.Label, out ConfigGamepadInputId rButtonA); - Enum.TryParse(_b.Label, out ConfigGamepadInputId rButtonB); - Enum.TryParse(_x.Label, out ConfigGamepadInputId rButtonX); - Enum.TryParse(_y.Label, out ConfigGamepadInputId rButtonY); - Enum.TryParse(_plus.Label, out ConfigGamepadInputId rButtonPlus); - Enum.TryParse(_r.Label, out ConfigGamepadInputId rButtonR); - Enum.TryParse(_zR.Label, out ConfigGamepadInputId rButtonZr); - Enum.TryParse(_rSl.Label, out ConfigGamepadInputId rButtonSl); - Enum.TryParse(_rSr.Label, out ConfigGamepadInputId rButtonSr); - - int.TryParse(_dsuServerPort.Buffer.Text, out int port); -#pragma warning restore CA1806, IDE0055 - - MotionConfigController motionConfig; - - if (_enableCemuHook.Active) - { - motionConfig = new CemuHookMotionConfigController - { - MotionBackend = MotionInputBackendType.CemuHook, - EnableMotion = _enableMotion.Active, - Sensitivity = (int)_sensitivity.Value, - GyroDeadzone = _gyroDeadzone.Value, - MirrorInput = _mirrorInput.Active, - Slot = (int)_slotNumber.Value, - AltSlot = (int)_altSlotNumber.Value, - DsuServerHost = _dsuServerHost.Buffer.Text, - DsuServerPort = port, - }; - } - else - { - motionConfig = new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - EnableMotion = _enableMotion.Active, - Sensitivity = (int)_sensitivity.Value, - GyroDeadzone = _gyroDeadzone.Value, - }; - } - - return new StandardControllerInputConfig - { - Backend = InputBackendType.GamepadSDL2, - Version = InputConfig.CurrentVersion, - Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0], - ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId), - PlayerIndex = _playerIndex, - DeadzoneLeft = (float)_controllerDeadzoneLeft.Value, - DeadzoneRight = (float)_controllerDeadzoneRight.Value, - RangeLeft = (float)_controllerRangeLeft.Value, - RangeRight = (float)_controllerRangeRight.Value, - TriggerThreshold = (float)_controllerTriggerThreshold.Value, - LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> - { - ButtonMinus = lButtonMinus, - ButtonL = lButtonL, - ButtonZl = lButtonZl, - ButtonSl = lButtonSl, - ButtonSr = lButtonSr, - DpadUp = lDPadUp, - DpadDown = lDPadDown, - DpadLeft = lDPadLeft, - DpadRight = lDPadRight, - }, - LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - InvertStickX = _invertLStickX.Active, - Joystick = lStick, - InvertStickY = _invertLStickY.Active, - StickButton = lStickButton, - Rotate90CW = _rotateL90CW.Active, - }, - RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> - { - ButtonA = rButtonA, - ButtonB = rButtonB, - ButtonX = rButtonX, - ButtonY = rButtonY, - ButtonPlus = rButtonPlus, - ButtonR = rButtonR, - ButtonZr = rButtonZr, - ButtonSl = rButtonSl, - ButtonSr = rButtonSr, - }, - RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - InvertStickX = _invertRStickX.Active, - Joystick = rStick, - InvertStickY = _invertRStickY.Active, - StickButton = rStickButton, - Rotate90CW = _rotateR90CW.Active, - }, - Motion = motionConfig, - Rumble = new RumbleConfigController - { - StrongRumble = (float)_controllerStrongRumble.Value, - WeakRumble = (float)_controllerWeakRumble.Value, - EnableRumble = _enableRumble.Active, - }, - }; - } - - if (!_inputDevice.ActiveId.StartsWith("disabled")) - { - GtkDialog.CreateErrorDialog("Invalid data detected in one or more fields; the configuration was not saved."); - } - - return null; - } - - private string GetProfileBasePath() - { - if (_inputDevice.ActiveId.StartsWith("keyboard")) - { - return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); - } - else if (_inputDevice.ActiveId.StartsWith("controller")) - { - return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller"); - } - - return AppDataManager.ProfilesDirPath; - } - - // - // Events - // - private void InputDevice_Changed(object sender, EventArgs args) - { - SetAvailableOptions(); - SetControllerSpecificFields(); - - _selectedGamepad?.Dispose(); - _selectedGamepad = null; - - if (_inputDevice.ActiveId != null) - { - SetProfiles(); - - string id = GetCurrentGamepadId(); - - if (_inputDevice.ActiveId.StartsWith("keyboard")) - { - if (_inputConfig is StandardKeyboardInputConfig) - { - SetValues(_inputConfig); - } - - if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver) - { - // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... - _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id); - } - else - { - _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - } - } - else if (_inputDevice.ActiveId.StartsWith("controller")) - { - if (_inputConfig is StandardControllerInputConfig) - { - SetValues(_inputConfig); - } - - _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - } - } - } - - private string GetCurrentGamepadId() - { - if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled") - { - return null; - } - - return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0]; - } - - private void Controller_Changed(object sender, EventArgs args) - { - SetControllerSpecificFields(); - } - - private IButtonAssigner CreateButtonAssigner(bool forStick) - { - IButtonAssigner assigner; - - if (_inputDevice.ActiveId.StartsWith("keyboard")) - { - assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad); - } - else if (_inputDevice.ActiveId.StartsWith("controller")) - { - assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick); - } - else - { - throw new Exception("Controller not supported"); - } - - return assigner; - } - - private void HandleButtonPressed(ToggleButton button, bool forStick) - { - if (_isWaitingForInput) - { - button.Active = false; - - return; - } - - _mousePressed = false; - - ButtonPressEvent += MouseClick; - - IButtonAssigner assigner = CreateButtonAssigner(forStick); - - _isWaitingForInput = true; - - // Open GTK3 keyboard for cancel operations - IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0"); - - Thread inputThread = new(() => - { - assigner.Initialize(); - - while (true) - { - Thread.Sleep(10); - assigner.ReadInput(); - - if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel()) - { - break; - } - } - - string pressedButton = assigner.GetPressedButton(); - - Application.Invoke(delegate - { - if (_middleMousePressed) - { - button.Label = "Unbound"; - } - else if (pressedButton != "") - { - button.Label = pressedButton; - } - - _middleMousePressed = false; - - ButtonPressEvent -= MouseClick; - keyboard.Dispose(); - - button.Active = false; - _isWaitingForInput = false; - }); - }) - { - Name = "GUI.InputThread", - IsBackground = true, - }; - inputThread.Start(); - } - - private void Button_Pressed(object sender, EventArgs args) - { - HandleButtonPressed((ToggleButton)sender, false); - } - - private void ButtonForStick_Pressed(object sender, EventArgs args) - { - HandleButtonPressed((ToggleButton)sender, true); - } - - private void MouseClick(object sender, ButtonPressEventArgs args) - { - _mousePressed = true; - _middleMousePressed = args.Event.Button == 2; - } - - private void SetProfiles() - { - _profile.RemoveAll(); - - string basePath = GetProfileBasePath(); - - if (!Directory.Exists(basePath)) - { - Directory.CreateDirectory(basePath); - } - - if (_inputDevice.ActiveId == null || _inputDevice.ActiveId.Equals("disabled")) - { - _profile.Append("default", "None"); - } - else - { - _profile.Append("default", "Default"); - - foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories)) - { - _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile)); - } - } - - _profile.SetActiveId("default"); - } - - private void ProfileLoad_Activated(object sender, EventArgs args) - { - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - - if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) - { - return; - } - - InputConfig config = null; - int pos = _profile.Active; - - if (_profile.ActiveId == "default") - { - if (_inputDevice.ActiveId.StartsWith("keyboard")) - { - config = new StandardKeyboardInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.WindowKeyboard, - Id = null, - ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig<Key> - { - 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<Key> - { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, - }, - - RightJoycon = new RightJoyconCommonConfig<Key> - { - 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<Key> - { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, - }, - }; - } - else if (_inputDevice.ActiveId.StartsWith("controller")) - { - bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo"); - - config = new StandardControllerInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.GamepadSDL2, - Id = null, - ControllerType = ControllerType.JoyconPair, - DeadzoneLeft = 0.1f, - DeadzoneRight = 0.1f, - RangeLeft = 1.0f, - RangeRight = 1.0f, - TriggerThreshold = 0.5f, - LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> - { - DpadUp = ConfigGamepadInputId.DpadUp, - DpadDown = ConfigGamepadInputId.DpadDown, - DpadLeft = ConfigGamepadInputId.DpadLeft, - DpadRight = ConfigGamepadInputId.DpadRight, - ButtonMinus = ConfigGamepadInputId.Minus, - ButtonL = ConfigGamepadInputId.LeftShoulder, - ButtonZl = ConfigGamepadInputId.LeftTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - - LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - Joystick = ConfigStickInputId.Left, - StickButton = ConfigGamepadInputId.LeftStick, - InvertStickX = false, - InvertStickY = false, - Rotate90CW = false, - }, - - RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> - { - ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, - ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, - ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, - ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, - ButtonPlus = ConfigGamepadInputId.Plus, - ButtonR = ConfigGamepadInputId.RightShoulder, - ButtonZr = ConfigGamepadInputId.RightTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - - RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> - { - Joystick = ConfigStickInputId.Right, - StickButton = ConfigGamepadInputId.RightStick, - InvertStickX = false, - InvertStickY = false, - Rotate90CW = false, - }, - - Motion = new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - EnableMotion = true, - Sensitivity = 100, - GyroDeadzone = 1, - }, - Rumble = new RumbleConfigController - { - StrongRumble = 1f, - WeakRumble = 1f, - EnableRumble = false, - }, - }; - } - } - else - { - string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); - - if (!File.Exists(path)) - { - if (pos >= 0) - { - _profile.Remove(pos); - } - - return; - } - - try - { - config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); - } - catch (JsonException) { } - } - - SetValues(config); - } - - private void ProfileAdd_Activated(object sender, EventArgs args) - { - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - - if (_inputDevice.ActiveId == "disabled") - { - return; - } - - InputConfig inputConfig = GetValues(); - ProfileDialog profileDialog = new(); - - if (inputConfig == null) - { - return; - } - - if (profileDialog.Run() == (int)ResponseType.Ok) - { - string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); - string jsonString = JsonHelper.Serialize(inputConfig, _serializerContext.InputConfig); - - File.WriteAllText(path, jsonString); - } - - profileDialog.Dispose(); - - SetProfiles(); - } - - private void ProfileRemove_Activated(object sender, EventArgs args) - { - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - - if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) - { - return; - } - - MessageDialog confirmDialog = GtkDialog.CreateConfirmationDialog("Deleting Profile", "This action is irreversible, are you sure you want to continue?"); - - if (confirmDialog.Run() == (int)ResponseType.Yes) - { - string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); - - if (File.Exists(path)) - { - File.Delete(path); - } - - SetProfiles(); - } - } - - private void SaveToggle_Activated(object sender, EventArgs args) - { - InputConfig inputConfig = GetValues(); - - var newConfig = new List<InputConfig>(); - newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); - - if (_inputConfig == null && inputConfig != null) - { - newConfig.Add(inputConfig); - } - else - { - if (_inputDevice.ActiveId == "disabled") - { - newConfig.Remove(_inputConfig); - } - else if (inputConfig != null) - { - int index = newConfig.IndexOf(_inputConfig); - - newConfig[index] = inputConfig; - } - } - - _mainWindow.RendererWidget?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); - - // Atomically replace and signal input change. - // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. - ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - - Dispose(); - } - - private void CloseToggle_Activated(object sender, EventArgs args) - { - Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/ControllerWindow.glade b/src/Ryujinx/UI/Windows/ControllerWindow.glade deleted file mode 100644 index e433f5cc..00000000 --- a/src/Ryujinx/UI/Windows/ControllerWindow.glade +++ /dev/null @@ -1,2241 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkAdjustment" id="_altSlotNumber"> - <property name="upper">4</property> - <property name="step_increment">1</property> - <property name="page_increment">4</property> - </object> - <object class="GtkAdjustment" id="_controllerStrongRumble"> - <property name="lower">0.1</property> - <property name="upper">10</property> - <property name="value">1.0</property> - <property name="step_increment">0.1</property> - <property name="page_increment">1.0</property> - </object> - <object class="GtkAdjustment" id="_controllerWeakRumble"> - <property name="lower">0.1</property> - <property name="upper">10</property> - <property name="value">1.0</property> - <property name="step_increment">0.1</property> - <property name="page_increment">1.0</property> - </object> - <object class="GtkAdjustment" id="_controllerDeadzoneLeft"> - <property name="upper">1</property> - <property name="value">0.050000000000000003</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_controllerDeadzoneRight"> - <property name="upper">1</property> - <property name="value">0.050000000000000003</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_controllerRangeLeft"> - <property name="upper">2</property> - <property name="value">1.000000000000000003</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_controllerRangeRight"> - <property name="upper">2</property> - <property name="value">1.000000000000000003</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_controllerTriggerThreshold"> - <property name="upper">1</property> - <property name="value">0.5</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_gyroDeadzone"> - <property name="upper">100</property> - <property name="value">0.01</property> - <property name="step_increment">0.01</property> - <property name="page_increment">0.10000000000000001</property> - <property name="page_size">0.10000000000000001</property> - </object> - <object class="GtkAdjustment" id="_sensitivity"> - <property name="upper">1000</property> - <property name="value">100</property> - <property name="step_increment">1</property> - <property name="page_increment">4</property> - </object> - <object class="GtkAdjustment" id="_slotNumber"> - <property name="upper">4</property> - <property name="step_increment">1</property> - <property name="page_increment">4</property> - </object> - <object class="GtkWindow" id="_controllerWin"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - Controller Settings</property> - <property name="modal">True</property> - <property name="window_position">center</property> - <property name="default_width">1200</property> - <property name="default_height">720</property> - <child type="titlebar"> - <placeholder/> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="HeadBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <child> - <object class="GtkBox" id="DeviceBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">5</property> - <property name="label" translatable="yes">Input Device</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_inputDevice"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="active">0</property> - <property name="active_id">disabled</property> - <items> - <item id="disabled" translatable="yes">Disabled</item> - </items> - <signal name="changed" handler="InputDevice_Changed" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="ControllerTypeBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">20</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">The controller's type</property> - <property name="halign">center</property> - <property name="margin_right">5</property> - <property name="label" translatable="yes">Controller Type:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_controllerType"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">The controller's type</property> - <property name="active">0</property> - <signal name="changed" handler="Controller_Changed" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="ProfileBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">20</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">5</property> - <property name="label" translatable="yes">Profile:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_profile"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">5</property> - <property name="active">0</property> - <property name="active_id">default</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton"> - <property name="label" translatable="yes">Load</property> - <property name="width_request">60</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">5</property> - <signal name="toggled" handler="ProfileLoad_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton"> - <property name="label" translatable="yes">Add</property> - <property name="width_request">60</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">5</property> - <signal name="toggled" handler="ProfileAdd_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkToggleButton"> - <property name="label" translatable="yes">Remove</property> - <property name="width_request">60</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <signal name="toggled" handler="ProfileRemove_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_settingsBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_top">5</property> - <child> - <object class="GtkBox" id="ButtonsBox"> - <property name="width_request">156</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Buttons</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">A</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">B</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">X</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Y</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_a"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_b"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_x"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_y"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">+</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">4</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">-</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_minus"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_plus"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="LeftStickBox"> - <property name="width_request">160</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Left Stick</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_bottom">5</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick Button</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lStickButton"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_leftStickKeyboard"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkToggleButton" id="_lStickDown"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lStickUp"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lStickLeft"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lStickRight"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick Down</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick Up</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick Right</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick Left</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_leftStickController"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">LStick</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lStick"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_invertLStickX"> - <property name="label" translatable="yes">Invert Stick X</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_invertLStickY"> - <property name="label" translatable="yes">Invert Stick Y</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_rotateL90CW"> - <property name="label" translatable="yes">Rotate 90° Clockwise</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_deadZoneLeftBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Deadzone Left</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerDeadzoneLeft</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_rangeLeftBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Range Left</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerRangeLeft</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TriggerBox"> - <property name="width_request">150</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Triggers</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">L</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">R</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_l"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_r"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">ZL</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">ZR</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_zL"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_zR"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_leftSideTriggerBox"> - <property name="name">_sideTriggerBox</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Left SL</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Left SR</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lSl"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_lSr"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_rightSideTriggerBox"> - <property name="name">_sideTriggerBox</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Right SL</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Right SR</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rSl"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rSr"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_triggerThresholdBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_left">10</property> - <property name="label" translatable="yes">Trigger Threshold</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerTriggerThreshold</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <child> - <object class="GtkBox" id="DPadBox"> - <property name="width_request">156</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">10</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Directional Pad</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Dpad Up</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Dpad Down</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Dpad Left</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Dpad Right</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_dpadUp"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_dpadDown"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_dpadLeft"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_dpadRight"> - <property name="label" translatable="yes"> </property> - <property name="width_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_rumbleBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Rumble</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_enableRumble"> - <property name="label" translatable="yes">Enable</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_StrongMultiBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Strong rumble multiplier</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerStrongRumble</property> - <property name="round_digits">1</property> - <property name="digits">1</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_WeakMultiBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Weak rumble multiplier</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerWeakRumble</property> - <property name="round_digits">1</property> - <property name="digits">1</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="RightStickBox"> - <property name="width_request">160</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Right Stick</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_bottom">5</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick Button</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStickButton"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_rightStickKeyboard"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick Up</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick Down</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick Left</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick Right</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStickUp"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStickDown"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStickLeft"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStickRight"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="_rightStickController"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">3</property> - <property name="column_spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="width_request">80</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">RStick</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_rStick"> - <property name="label" translatable="yes"> </property> - <property name="width_request">65</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_invertRStickX"> - <property name="label" translatable="yes">Invert Stick X</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_invertRStickY"> - <property name="label" translatable="yes">Invert Stick Y</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_rotateR90CW"> - <property name="label" translatable="yes">Rotate 90° Clockwise</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_deadZoneRightBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Deadzone Right</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerDeadzoneRight</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_rangeRightBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Range Right</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_controllerRangeRight</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_motionBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="orientation">vertical</property> - <property name="spacing">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - <property name="label" translatable="yes">Motion</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_enableMotion"> - <property name="label" translatable="yes">Enable Motion Controls</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_enableCemuHook"> - <property name="label" translatable="yes">Use CemuHook compatible motion</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_motionControllerSlot"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">17</property> - <property name="label" translatable="yes">Controller Slot</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_slot"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">10</property> - <property name="adjustment">_slotNumber</property> - <property name="climb_rate">1</property> - <property name="snap_to_ticks">True</property> - <property name="numeric">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">5</property> - <property name="label" translatable="yes">Gyro Sensitivity %</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinButton"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="text" translatable="yes">0</property> - <property name="adjustment">_sensitivity</property> - <property name="climb_rate">1</property> - <property name="snap_to_ticks">True</property> - <property name="numeric">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_motionAltBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_mirrorInput"> - <property name="label" translatable="yes">Mirror Input</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">10</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Right JoyCon Slot</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_slotRight"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="text" translatable="yes">0</property> - <property name="adjustment">_altSlotNumber</property> - <property name="climb_rate">1</property> - <property name="snap_to_ticks">True</property> - <property name="numeric">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_dsuServerHostBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">30</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Server Host</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_dsuServerHost"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_dsuServerPortBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">30</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Server Port</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_dsuServerPort"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">7</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Gyro Deadzone</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">8</property> - </packing> - </child> - <child> - <object class="GtkScale"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">_gyroDeadzone</property> - <property name="round_digits">2</property> - <property name="digits">2</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">9</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkImage" id="_controllerImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">20</property> - <property name="margin_top">5</property> - <property name="margin_bottom">5</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_right">5</property> - <property name="margin_top">3</property> - <property name="margin_bottom">3</property> - <property name="layout_style">end</property> - <child> - <object class="GtkToggleButton" id="SaveToggle"> - <property name="label" translatable="yes">Save</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <signal name="toggled" handler="SaveToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="CloseToggle"> - <property name="label" translatable="yes">Close</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_left">4</property> - <signal name="toggled" handler="CloseToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Windows/DlcWindow.cs b/src/Ryujinx/UI/Windows/DlcWindow.cs deleted file mode 100644 index aed1a015..00000000 --- a/src/Ryujinx/UI/Windows/DlcWindow.cs +++ /dev/null @@ -1,280 +0,0 @@ -using Gtk; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.FileSystem; -using Ryujinx.UI.Widgets; -using System; -using System.Collections.Generic; -using System.IO; -using GUI = Gtk.Builder.ObjectAttribute; - -namespace Ryujinx.UI.Windows -{ - public class DlcWindow : Window - { - private readonly VirtualFileSystem _virtualFileSystem; - private readonly string _titleId; - private readonly string _dlcJsonPath; - private readonly List<DownloadableContentContainer> _dlcContainerList; - - private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] Label _baseTitleInfoLabel; - [GUI] TreeView _dlcTreeView; - [GUI] TreeSelection _dlcTreeSelection; -#pragma warning restore CS0649, IDE0044 - - public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } - - private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) - { - builder.Autoconnect(this); - - _titleId = titleId; - _virtualFileSystem = virtualFileSystem; - _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json"); - _baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]"; - - try - { - _dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, _serializerContext.ListDownloadableContentContainer); - } - catch - { - _dlcContainerList = new List<DownloadableContentContainer>(); - } - - _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); - - CellRendererToggle enableToggle = new(); - enableToggle.Toggled += (sender, args) => - { - _dlcTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); - bool newValue = !(bool)_dlcTreeView.Model.GetValue(treeIter, 0); - _dlcTreeView.Model.SetValue(treeIter, 0, newValue); - - if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) - { - do - { - _dlcTreeView.Model.SetValue(childIter, 0, newValue); - } - while (_dlcTreeView.Model.IterNext(ref childIter)); - } - }; - - _dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0); - _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1); - _dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); - - foreach (DownloadableContentContainer dlcContainer in _dlcContainerList) - { - if (File.Exists(dlcContainer.ContainerPath)) - { - // The parent tree item has its own "enabled" check box, but it's the actual - // nca entries that store the enabled / disabled state. A bit of a UI inconsistency. - // Maybe a tri-state check box would be better, but for now we check the parent - // "enabled" box if all child NCAs are enabled. Usually fine since each nsp has only one nca. - bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled); - TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath); - - using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath); - - PartitionFileSystem pfs = new(); - pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure(); - - _virtualFileSystem.ImportTickets(pfs); - - foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath); - - if (nca != null) - { - ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.FullPath); - } - } - } - else - { - // DLC file moved or renamed. Allow the user to remove it without crashing the whole dialog. - TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}"); - } - } - } - - private Nca TryCreateNca(IStorage ncaStorage, string containerPath) - { - try - { - return new Nca(_virtualFileSystem.KeySet, ncaStorage); - } - catch (Exception exception) - { - GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}"); - } - - return null; - } - - private void AddButton_Clicked(object sender, EventArgs args) - { - FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel") - { - SelectMultiple = true, - }; - - FileFilter filter = new() - { - Name = "Switch Game DLCs", - }; - filter.AddPattern("*.nsp"); - - fileChooser.AddFilter(filter); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - foreach (string containerPath in fileChooser.Filenames) - { - if (!File.Exists(containerPath)) - { - return; - } - - using FileStream containerFile = File.OpenRead(containerPath); - - PartitionFileSystem pfs = new(); - pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure(); - bool containsDlc = false; - - _virtualFileSystem.ImportTickets(pfs); - - TreeIter? parentIter = null; - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath); - - if (nca == null) - { - continue; - } - - if (nca.Header.ContentType == NcaContentType.PublicData) - { - if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId) - { - break; - } - - parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath); - - ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath); - containsDlc = true; - } - } - - if (!containsDlc) - { - GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!"); - } - } - } - - fileChooser.Dispose(); - } - - private void RemoveButton_Clicked(object sender, EventArgs args) - { - if (_dlcTreeSelection.GetSelected(out ITreeModel treeModel, out TreeIter treeIter)) - { - if (_dlcTreeView.Model.IterParent(out TreeIter parentIter, treeIter) && _dlcTreeView.Model.IterNChildren(parentIter) <= 1) - { - ((TreeStore)treeModel).Remove(ref parentIter); - } - else - { - ((TreeStore)treeModel).Remove(ref treeIter); - } - } - } - - private void RemoveAllButton_Clicked(object sender, EventArgs args) - { - List<TreeIter> toRemove = new(); - - if (_dlcTreeView.Model.GetIterFirst(out TreeIter iter)) - { - do - { - toRemove.Add(iter); - } - while (_dlcTreeView.Model.IterNext(ref iter)); - } - - foreach (TreeIter i in toRemove) - { - TreeIter j = i; - ((TreeStore)_dlcTreeView.Model).Remove(ref j); - } - } - - private void SaveButton_Clicked(object sender, EventArgs args) - { - _dlcContainerList.Clear(); - - if (_dlcTreeView.Model.GetIterFirst(out TreeIter parentIter)) - { - do - { - if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) - { - DownloadableContentContainer dlcContainer = new() - { - ContainerPath = (string)_dlcTreeView.Model.GetValue(parentIter, 2), - DownloadableContentNcaList = new List<DownloadableContentNca>(), - }; - - do - { - dlcContainer.DownloadableContentNcaList.Add(new DownloadableContentNca - { - Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), - TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), - FullPath = (string)_dlcTreeView.Model.GetValue(childIter, 2), - }); - } - while (_dlcTreeView.Model.IterNext(ref childIter)); - - _dlcContainerList.Add(dlcContainer); - } - } - while (_dlcTreeView.Model.IterNext(ref parentIter)); - } - - JsonHelper.SerializeToFile(_dlcJsonPath, _dlcContainerList, _serializerContext.ListDownloadableContentContainer); - - Dispose(); - } - - private void CancelButton_Clicked(object sender, EventArgs args) - { - Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/DlcWindow.glade b/src/Ryujinx/UI/Windows/DlcWindow.glade deleted file mode 100644 index bdb0e647..00000000 --- a/src/Ryujinx/UI/Windows/DlcWindow.glade +++ /dev/null @@ -1,202 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkWindow" id="_dlcWindow"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - DLC Manager</property> - <property name="modal">True</property> - <property name="window_position">center</property> - <property name="default_width">550</property> - <property name="default_height">350</property> - <child> - <object class="GtkBox" id="MainBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="DlcBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel" id="_baseTitleInfoLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="label" translatable="yes">Available DLC</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkTreeView" id="_dlcTreeView"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_clickable">False</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="_dlcTreeSelection"/> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="layout_style">start</property> - <child> - <object class="GtkButton" id="_addUpdate"> - <property name="label" translatable="yes">Add</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Adds a DLC to this list</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="AddButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_removeUpdate"> - <property name="label" translatable="yes">Remove</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Removes the selected DLC</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_removeAllButton"> - <property name="label" translatable="yes">Remove All</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Removes all DLCs</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="_saveButton"> - <property name="label" translatable="yes">Save</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_cancelButton"> - <property name="label" translatable="yes">Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <child type="titlebar"> - <placeholder/> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml new file mode 100644 index 00000000..99cf28e7 --- /dev/null +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml @@ -0,0 +1,192 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + Width="500" + Height="380" + mc:Ignorable="d" + x:DataType="viewModels:DownloadableContentManagerViewModel" + Focusable="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Panel + Margin="0 0 0 10" + Grid.Row="0"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <TextBlock + Grid.Column="0" + Text="{Binding UpdateCount}" /> + <StackPanel + Margin="10 0" + Grid.Column="1" + Orientation="Horizontal"> + <Button + Name="EnableAllButton" + MinWidth="90" + Margin="5" + Command="{Binding EnableAll}"> + <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> + </Button> + <Button + Name="DisableAllButton" + MinWidth="90" + Margin="5" + Command="{Binding DisableAll}"> + <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> + </Button> + </StackPanel> + <TextBox + Grid.Column="2" + MinHeight="29" + MaxHeight="29" + HorizontalAlignment="Stretch" + Watermark="{locale:Locale Search}" + Text="{Binding Search}" /> + </Grid> + </Panel> + <Border + Grid.Row="1" + Margin="0 0 0 24" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1" + CornerRadius="5" + Padding="2.5"> + <ListBox + AutoScrollToSelectedItem="False" + SelectionMode="Multiple, Toggle" + Background="Transparent" + SelectionChanged="OnSelectionChanged" + SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}" + ItemsSource="{Binding Views}"> + <ListBox.DataTemplates> + <DataTemplate + DataType="models:DownloadableContentModel"> + <Panel Margin="10"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <Grid + Grid.Column="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <TextBlock + Grid.Column="0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + MaxLines="2" + TextWrapping="Wrap" + TextTrimming="CharacterEllipsis" + Text="{Binding FileName}" /> + <TextBlock + Grid.Column="1" + Margin="10 0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{Binding TitleId}" /> + </Grid> + <StackPanel + Grid.Column="1" + Spacing="10" + Orientation="Horizontal" + HorizontalAlignment="Right"> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="OpenLocation"> + <ui:SymbolIcon + Symbol="OpenFolder" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="RemoveDLC"> + <ui:SymbolIcon + Symbol="Cancel" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + </StackPanel> + </Grid> + </Panel> + </DataTemplate> + </ListBox.DataTemplates> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Background" Value="Transparent" /> + </Style> + </ListBox.Styles> + </ListBox> + </Border> + <Panel + Grid.Row="2" + HorizontalAlignment="Stretch"> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Left"> + <Button + Name="AddButton" + MinWidth="90" + Margin="5" + Command="{Binding Add}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveAllButton" + MinWidth="90" + Margin="5" + Command="{Binding RemoveAll}"> + <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> + </Button> + </StackPanel> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + Click="SaveAndClose"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Click="Close"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </StackPanel> + </Panel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs new file mode 100644 index 00000000..0c02fa0f --- /dev/null +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -0,0 +1,115 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +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 System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class DownloadableContentManagerWindow : UserControl + { + public DownloadableContentManagerViewModel ViewModel; + + public DownloadableContentManagerWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId) + { + DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId); + + InitializeComponent(); + } + + public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId), + Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")), + }; + + Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await ContentDialogHelper.ShowAsync(contentDialog); + } + + private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs) + { + ViewModel.Save(); + ((ContentDialog)Parent).Hide(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + private void RemoveDLC(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is DownloadableContentModel model) + { + ViewModel.Remove(model); + } + } + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is DownloadableContentModel model) + { + OpenHelper.LocateFile(model.ContainerPath); + } + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + foreach (var content in e.AddedItems) + { + if (content is DownloadableContentModel model) + { + var index = ViewModel.DownloadableContents.IndexOf(model); + + if (index != -1) + { + ViewModel.DownloadableContents[index].Enabled = true; + } + } + } + + foreach (var content in e.RemovedItems) + { + if (content is DownloadableContentModel model) + { + var index = ViewModel.DownloadableContents.IndexOf(model); + + if (index != -1) + { + ViewModel.DownloadableContents[index].Enabled = false; + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/IconColorPicker.cs b/src/Ryujinx/UI/Windows/IconColorPicker.cs new file mode 100644 index 00000000..4c75a5ff --- /dev/null +++ b/src/Ryujinx/UI/Windows/IconColorPicker.cs @@ -0,0 +1,194 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Windows +{ + static class IconColorPicker + { + private const int ColorsPerLine = 64; + private const int TotalColors = ColorsPerLine * ColorsPerLine; + + private const int UvQuantBits = 3; + private const int UvQuantShift = BitsPerComponent - UvQuantBits; + + private const int SatQuantBits = 5; + private const int SatQuantShift = BitsPerComponent - SatQuantBits; + + private const int BitsPerComponent = 8; + + private const int CutOffLuminosity = 64; + + private readonly struct PaletteColor + { + public int Qck { get; } + public byte R { get; } + public byte G { get; } + public byte B { get; } + + public PaletteColor(int qck, byte r, byte g, byte b) + { + Qck = qck; + R = r; + G = g; + B = b; + } + } + + public static Color GetFilteredColor(Image<Bgra32> image) + { + var color = GetColor(image).ToPixel<Bgra32>(); + + // We don't want colors that are too dark. + // If the color is too dark, make it brighter by reducing the range + // and adding a constant color. + int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B); + if (luminosity < CutOffLuminosity) + { + color = Color.FromRgb( + (byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue)); + } + + return color; + } + + public static Color GetColor(Image<Bgra32> image) + { + var colors = new PaletteColor[TotalColors]; + + var dominantColorBin = new Dictionary<int, int>(); + + var buffer = GetBuffer(image); + + int w = image.Width; + + int w8 = w << 8; + int h8 = image.Height << 8; + +#pragma warning disable IDE0059 // Unnecessary assignment + int xStep = w8 / ColorsPerLine; + int yStep = h8 / ColorsPerLine; +#pragma warning restore IDE0059 + + int i = 0; + int maxHitCount = 0; + + for (int y = 0; y < image.Height; y++) + { + int yOffset = y * image.Width; + + for (int x = 0; x < image.Width && i < TotalColors; x++) + { + int offset = x + yOffset; + + byte cb = buffer[offset].B; + byte cg = buffer[offset].G; + byte cr = buffer[offset].R; + + var qck = GetQuantizedColorKey(cr, cg, cb); + + if (dominantColorBin.TryGetValue(qck, out int hitCount)) + { + dominantColorBin[qck] = hitCount + 1; + + if (maxHitCount < hitCount) + { + maxHitCount = hitCount; + } + } + else + { + dominantColorBin.Add(qck, 1); + } + + colors[i++] = new PaletteColor(qck, cr, cg, cb); + } + } + + int highScore = -1; + PaletteColor bestCandidate = default; + + for (i = 0; i < TotalColors; i++) + { + var score = GetColorScore(dominantColorBin, maxHitCount, colors[i]); + + if (highScore < score) + { + highScore = score; + bestCandidate = colors[i]; + } + } + + return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B); + } + + public static Bgra32[] GetBuffer(Image<Bgra32> image) + { + return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty<Bgra32>(); + } + + private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color) + { + var hitCount = dominantColorBin[color.Qck]; + var balancedHitCount = BalanceHitCount(hitCount, maxHitCount); + var quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift; + var value = GetColorValue(color); + + // If the color is rarely used on the image, + // then chances are that theres a better candidate, even if the saturation value + // is high. By multiplying the saturation value with a weight, we can lower + // it if the color is almost never used (hit count is low). + var satWeighted = quantSat; + var satWeight = balancedHitCount << 5; + if (satWeight < 0x100) + { + satWeighted = (satWeighted * satWeight) >> 8; + } + + // Compute score from saturation and dominance of the color. + // We prefer more vivid colors over dominant ones, so give more weight to the saturation. + var score = ((satWeighted << 1) + balancedHitCount) * value; + + return score; + } + + private static int BalanceHitCount(int hitCount, int maxHitCount) + { + return (hitCount << 8) / maxHitCount; + } + + private static int GetColorApproximateLuminosity(byte r, byte g, byte b) + { + return (r + g + b) / 3; + } + + private static int GetColorSaturation(PaletteColor color) + { + int cMax = Math.Max(Math.Max(color.R, color.G), color.B); + + if (cMax == 0) + { + return 0; + } + + int cMin = Math.Min(Math.Min(color.R, color.G), color.B); + int delta = cMax - cMin; + return (delta << 8) / cMax; + } + + private static int GetColorValue(PaletteColor color) + { + return Math.Max(Math.Max(color.R, color.G), color.B); + } + + private static int GetQuantizedColorKey(byte r, byte g, byte b) + { + int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; + int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; + return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits); + } + } +} diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml new file mode 100644 index 00000000..4def7c28 --- /dev/null +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml @@ -0,0 +1,205 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.UI.Windows.MainWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" + xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main" + Cursor="{Binding Cursor}" + Title="{Binding Title}" + WindowState="{Binding WindowState}" + Width="{Binding WindowWidth}" + Height="{Binding WindowHeight}" + MinWidth="1092" + MinHeight="672" + d:DesignHeight="720" + d:DesignWidth="1280" + x:DataType="viewModels:MainWindowViewModel" + mc:Ignorable="d" + WindowStartupLocation="Manual" + Focusable="True"> + <Window.Styles> + <Style Selector="TitleBar:fullscreen"> + <Setter Property="Background" Value="#000000" /> + </Style> + </Window.Styles> + <Design.DataContext> + <viewModels:MainWindowViewModel /> + </Design.DataContext> + <Window.Resources> + <helpers:BitmapArrayValueConverter x:Key="ByteImage" /> + </Window.Resources> + <Window.KeyBindings> + <KeyBinding Gesture="Alt+Return" Command="{Binding ToggleFullscreen}" /> + <KeyBinding Gesture="F11" Command="{Binding ToggleFullscreen}" /> + <KeyBinding Gesture="Ctrl+Cmd+F" Command="{Binding ToggleFullscreen}" /> + <KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" /> + <KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" /> + </Window.KeyBindings> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> + <Grid + Grid.Row="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <StackPanel + Name="MenuBar" + MinHeight="35" + Grid.Row="0" + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + IsVisible="{Binding ShowMenuAndStatusBar}" + Orientation="Vertical"> + <main:MainMenuBarView + Name="MenuBarView" /> + </StackPanel> + <ContentControl + Name="MainContent" + Grid.Row="1" + Padding="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="0,0,0,0" + DockPanel.Dock="Top" + IsVisible="{Binding ShowContent}"> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="GameLibrary"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <main:MainViewControls + Name="ViewControls" + Grid.Row="0"/> + <controls:ApplicationListView + x:Name="ApplicationList" + Grid.Row="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalContentAlignment="Stretch" + VerticalContentAlignment="Stretch" + IsVisible="{Binding IsList}" /> + <controls:ApplicationGridView + x:Name="ApplicationGrid" + Grid.Row="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalContentAlignment="Stretch" + VerticalContentAlignment="Stretch" + IsVisible="{Binding IsGrid}" /> + </Grid> + </ContentControl> + <Grid + Grid.Row="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Background="{DynamicResource ThemeContentBackgroundColor}" + IsVisible="{Binding ShowLoadProgress}" + Name="LoadingView" + ZIndex="1000"> + <Grid + Margin="40" + HorizontalAlignment="Center" + VerticalAlignment="Center" + IsVisible="{Binding ShowLoadProgress}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Border + Grid.RowSpan="2" + Grid.Column="0" + Width="256" + Height="256" + Margin="10" + Padding="4" + BorderBrush="Black" + BorderThickness="2" + BoxShadow="4 4 32 8 #40000000" + CornerRadius="3" + IsVisible="{Binding ShowLoadProgress}"> + <Image + Width="256" + Height="256" + IsVisible="{Binding ShowLoadProgress}" + Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" /> + </Border> + <Grid + Grid.Column="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + IsVisible="{Binding ShowLoadProgress}"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + Grid.Row="0" + Margin="10" + FontSize="30" + FontWeight="Bold" + IsVisible="{Binding ShowLoadProgress}" + Text="{Binding LoadHeading}" + TextAlignment="Start" + TextWrapping="Wrap" + MaxWidth="500" /> + <Border + Grid.Row="1" + Margin="10" + Padding="0" + HorizontalAlignment="Stretch" + BorderBrush="{Binding ProgressBarBackgroundColor}" + BorderThickness="1" + ClipToBounds="True" + CornerRadius="5" + IsVisible="{Binding ShowLoadProgress}"> + <ProgressBar + Height="10" + MinWidth="500" + Margin="0" + Padding="0" + HorizontalAlignment="Stretch" + ClipToBounds="True" + CornerRadius="5" + Foreground="{Binding ProgressBarForegroundColor}" + IsIndeterminate="{Binding IsLoadingIndeterminate}" + IsVisible="{Binding ShowLoadProgress}" + Maximum="{Binding ProgressMaximum}" + Minimum="0" + Value="{Binding ProgressValue}" /> + </Border> + <TextBlock + Grid.Row="2" + Margin="10" + FontSize="18" + IsVisible="{Binding ShowLoadProgress}" + Text="{Binding CacheLoadStatus}" + TextAlignment="Start" + MaxWidth="500" /> + </Grid> + </Grid> + </Grid> + <main:MainStatusBarView + Name="StatusBarView" + Grid.Row="2" /> + </Grid> + </Grid> +</window:StyleableWindow> diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs new file mode 100644 index 00000000..33a9af5b --- /dev/null +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -0,0 +1,551 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Applet; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Input.HLE; +using Ryujinx.Input.SDL2; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.IO; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class MainWindow : StyleableWindow + { + internal static MainWindowViewModel MainWindowViewModel { get; private set; } + + private bool _isLoading; + + private UserChannelPersistence _userChannelPersistence; + private static bool _deferLoad; + private static string _launchPath; + private static bool _startFullscreen; + internal readonly AvaHostUIHandler UiHandler; + + public VirtualFileSystem VirtualFileSystem { get; private set; } + public ContentManager ContentManager { get; private set; } + public AccountManager AccountManager { get; private set; } + + public LibHacHorizonManager LibHacHorizonManager { get; private set; } + + public InputManager InputManager { get; private set; } + + internal MainWindowViewModel ViewModel { get; private set; } + public SettingsWindow SettingsWindow { get; set; } + + public static bool ShowKeyErrorOnLoad { get; set; } + public ApplicationLibrary ApplicationLibrary { get; set; } + + public MainWindow() + { + ViewModel = new MainWindowViewModel(); + + MainWindowViewModel = ViewModel; + + DataContext = ViewModel; + + SetWindowSizePosition(); + + InitializeComponent(); + Load(); + + UiHandler = new AvaHostUIHandler(this); + + ViewModel.Title = $"Ryujinx {Program.Version}"; + + // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. + double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight; + Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; + Width /= Program.WindowScaleFactor; + + if (Program.PreviewerDetached) + { + InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); + + this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged); + this.ScalingChanged += OnScalingChanged; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + NotificationHelper.SetNotificationManager(this); + } + + private void IsActiveChanged(bool obj) + { + ViewModel.IsActive = obj; + } + + private void OnScalingChanged(object sender, EventArgs e) + { + Program.DesktopScaleFactor = this.RenderScaling; + } + + private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e) + { + Dispatcher.UIThread.Post(() => + { + ViewModel.Applications.Add(e.AppData); + }); + } + + private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound); + + Dispatcher.UIThread.Post(() => + { + ViewModel.StatusBarProgressValue = e.NumAppsLoaded; + ViewModel.StatusBarProgressMaximum = e.NumAppsFound; + + if (e.NumAppsFound == 0) + { + StatusBarView.LoadProgressBar.IsVisible = false; + } + + if (e.NumAppsLoaded == e.NumAppsFound) + { + StatusBarView.LoadProgressBar.IsVisible = false; + } + }); + } + + public void Application_Opened(object sender, ApplicationOpenedEventArgs args) + { + if (args.Application != null) + { + ViewModel.SelectedIcon = args.Application.Icon; + + string path = new FileInfo(args.Application.Path).FullName; + + ViewModel.LoadApplication(path).Wait(); + } + + args.Handled = true; + } + + internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg) + { + _deferLoad = true; + _launchPath = launchPathArg; + _startFullscreen = startFullscreenArg; + } + + public void SwitchToGameControl(bool startFullscreen = false) + { + ViewModel.ShowLoadProgress = false; + ViewModel.ShowContent = true; + ViewModel.IsLoadingIndeterminate = false; + + if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + } + + public void ShowLoading(bool startFullscreen = false) + { + ViewModel.ShowContent = false; + ViewModel.ShowLoadProgress = true; + ViewModel.IsLoadingIndeterminate = true; + + if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + } + + private void Initialize() + { + _userChannelPersistence = new UserChannelPersistence(); + VirtualFileSystem = VirtualFileSystem.CreateInstance(); + LibHacHorizonManager = new LibHacHorizonManager(); + ContentManager = new ContentManager(VirtualFileSystem); + + LibHacHorizonManager.InitializeFsServer(VirtualFileSystem); + LibHacHorizonManager.InitializeArpServer(); + LibHacHorizonManager.InitializeBcatServer(); + LibHacHorizonManager.InitializeSystemClients(); + + ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem); + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient); + + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile); + + VirtualFileSystem.ReloadKeySet(); + + ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient); + } + + [SupportedOSPlatform("linux")] + private static async Task ShowVmMaxMapCountWarning() + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary, + LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary] + ); + } + + [SupportedOSPlatform("linux")] + private static async Task ShowVmMaxMapCountDialog() + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary, + LinuxHelper.RecommendedVmMaxMapCount); + + UserResult response = await ContentDialogHelper.ShowTextDialog( + $"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}", + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help + ); + + int rc; + + switch (response) + { + case UserResult.Ok: + rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); + } + break; + case UserResult.No: + rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); + } + break; + } + } + + private async Task CheckLaunchState() + { + if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) + { + Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})"); + + if (LinuxHelper.PkExecPath is not null) + { + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog); + } + else + { + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning); + } + } + + if (!ShowKeyErrorOnLoad) + { + if (_deferLoad) + { + _deferLoad = false; + + ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait(); + } + } + else + { + ShowKeyErrorOnLoad = false; + + await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) + { + await Updater.BeginParse(this, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + } + + private void Load() + { + StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged; + + ApplicationGrid.ApplicationOpened += Application_Opened; + + ApplicationGrid.DataContext = ViewModel; + + ApplicationList.ApplicationOpened += Application_Opened; + + ApplicationList.DataContext = ViewModel; + } + + private void SetWindowSizePosition() + { + PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); + + ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; + ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; + + ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal; + + if (CheckScreenBounds(savedPoint)) + { + Position = savedPoint; + } + else + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + } + + private bool CheckScreenBounds(PixelPoint configPoint) + { + for (int i = 0; i < Screens.ScreenCount; i++) + { + if (Screens.All[i].Bounds.Contains(configPoint)) + { + return true; + } + } + + Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center."); + return false; + } + + private void SaveWindowSizePosition() + { + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width; + + ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; + + ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized; + + MainWindowViewModel.SaveConfig(); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + + Initialize(); + + ViewModel.Initialize( + ContentManager, + StorageProvider, + ApplicationLibrary, + VirtualFileSystem, + AccountManager, + InputManager, + _userChannelPersistence, + LibHacHorizonManager, + UiHandler, + ShowLoading, + SwitchToGameControl, + SetMainContent, + this); + + ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; + ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; + + ViewModel.RefreshFirmwareStatus(); + + LoadApplications(); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + CheckLaunchState(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void SetMainContent(Control content = null) + { + content ??= GameLibrary; + + if (MainContent.Content != content) + { + MainContent.Content = content; + } + } + + public static void UpdateGraphicsConfig() + { +#pragma warning disable IDE0055 // Disable formatting + GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale; + GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; + GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; +#pragma warning restore IDE0055 + } + + private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e) + { + var volumeSplitButton = sender as ToggleSplitButton; + if (ViewModel.IsGameRunning) + { + if (!volumeSplitButton.IsChecked) + { + ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute); + } + else + { + ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume(); + ViewModel.AppHost.Device.SetVolume(0); + } + + ViewModel.Volume = ViewModel.AppHost.Device.GetVolume(); + } + } + + protected override void OnClosing(WindowClosingEventArgs e) + { + if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit) + { + e.Cancel = true; + + ConfirmExit(); + + return; + } + + ViewModel.IsClosing = true; + + if (ViewModel.AppHost != null) + { + ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit; + ViewModel.AppHost.AppExit += (sender, e) => + { + ViewModel.AppHost = null; + + Dispatcher.UIThread.Post(() => + { + MainContent = null; + + Close(); + }); + }; + ViewModel.AppHost?.Stop(); + + e.Cancel = true; + + return; + } + + SaveWindowSizePosition(); + + ApplicationLibrary.CancelLoading(); + InputManager.Dispose(); + Program.Exit(); + + base.OnClosing(e); + } + + private void ConfirmExit() + { + Dispatcher.UIThread.InvokeAsync(async () => + { + ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog(); + + if (ViewModel.IsClosing) + { + Close(); + } + }); + } + + public void LoadApplications() + { + ViewModel.Applications.Clear(); + + StatusBarView.LoadProgressBar.IsVisible = true; + ViewModel.StatusBarProgressMaximum = 0; + ViewModel.StatusBarProgressValue = 0; + + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); + + ReloadGameList(); + } + + public void ToggleFileType(string fileType) + { + _ = fileType switch + { +#pragma warning disable IDE0055 // Disable formatting + "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 + }; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + LoadApplications(); + } + + private void ReloadGameList() + { + if (_isLoading) + { + return; + } + + _isLoading = true; + + Thread applicationLibraryThread = new(() => + { + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + + _isLoading = false; + }) + { + Name = "GUI.ApplicationLibraryThread", + IsBackground = true, + }; + applicationLibraryThread.Start(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml new file mode 100644 index 00000000..0ed05ce3 --- /dev/null +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml @@ -0,0 +1,179 @@ +<UserControl + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + Width="500" + Height="380" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.UI.Windows.ModManagerWindow" + x:CompileBindings="True" + x:DataType="viewModels:ModManagerViewModel" + Focusable="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Panel + Margin="0 0 0 10" + Grid.Row="0"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <TextBlock + Grid.Column="0" + Text="{Binding ModCount}" /> + <StackPanel + Margin="10 0" + Grid.Column="1" + Orientation="Horizontal"> + <Button + Name="EnableAllButton" + MinWidth="90" + Margin="5" + Command="{Binding EnableAll}"> + <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> + </Button> + <Button + Name="DisableAllButton" + MinWidth="90" + Margin="5" + Command="{Binding DisableAll}"> + <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> + </Button> + </StackPanel> + <TextBox + Grid.Column="2" + MinHeight="27" + MaxHeight="27" + HorizontalAlignment="Stretch" + Watermark="{locale:Locale Search}" + Text="{Binding Search}" /> + </Grid> + </Panel> + <Border + Grid.Row="1" + Margin="0 0 0 24" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1" + CornerRadius="5" + Padding="2.5"> + <ListBox + AutoScrollToSelectedItem="False" + SelectionMode="Multiple, Toggle" + Background="Transparent" + SelectionChanged="OnSelectionChanged" + SelectedItems="{Binding SelectedMods, Mode=TwoWay}" + ItemsSource="{Binding Views}"> + <ListBox.DataTemplates> + <DataTemplate + DataType="models:ModModel"> + <Panel Margin="10"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <TextBlock + HorizontalAlignment="Left" + VerticalAlignment="Center" + MaxLines="2" + TextWrapping="Wrap" + TextTrimming="CharacterEllipsis" + Text="{Binding Name}" /> + <StackPanel + Grid.Column="1" + Spacing="10" + Orientation="Horizontal" + HorizontalAlignment="Right"> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="OpenLocation"> + <ui:SymbolIcon + Symbol="OpenFolder" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="DeleteMod"> + <ui:SymbolIcon + Symbol="Cancel" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + </StackPanel> + </Grid> + </Panel> + </DataTemplate> + </ListBox.DataTemplates> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Background" Value="Transparent" /> + </Style> + </ListBox.Styles> + </ListBox> + </Border> + <Panel + Grid.Row="2" + HorizontalAlignment="Stretch"> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Left"> + <Button + Name="AddButton" + MinWidth="90" + Margin="5" + Command="{Binding Add}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveAllButton" + MinWidth="90" + Margin="5" + Click="DeleteAll"> + <TextBlock Text="{locale:Locale ModManagerDeleteAllButton}" /> + </Button> + </StackPanel> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + Click="SaveAndClose"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Click="Close"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </StackPanel> + </Panel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs new file mode 100644 index 00000000..d9ae0d4f --- /dev/null +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs @@ -0,0 +1,139 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +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 System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class ModManagerWindow : UserControl + { + public ModManagerViewModel ViewModel; + + public ModManagerWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public ModManagerWindow(ulong titleId) + { + DataContext = ViewModel = new ModManagerViewModel(titleId); + + InitializeComponent(); + } + + public static async Task Show(ulong titleId, string titleName) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new ModManagerWindow(titleId), + Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")), + }; + + Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await contentDialog.ShowAsync(); + } + + private void SaveAndClose(object sender, RoutedEventArgs e) + { + ViewModel.Save(); + ((ContentDialog)Parent).Hide(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + private async void DeleteMod(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogModManagerDeletionWarningMessage, model.Name), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Delete(model); + } + } + } + } + + private async void DeleteAll(object sender, RoutedEventArgs e) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance[LocaleKeys.DialogModManagerDeletionAllWarningMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.DeleteAll(); + } + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + OpenHelper.OpenFolder(model.Path); + } + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + foreach (var content in e.AddedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = true; + } + } + } + + foreach (var content in e.RemovedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = false; + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml new file mode 100644 index 00000000..de3c2291 --- /dev/null +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -0,0 +1,130 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.UI.Windows.SettingsWindow" + xmlns="https://github.com/avaloniaui" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" + Width="1100" + Height="768" + MinWidth="800" + MinHeight="480" + WindowStartupLocation="CenterOwner" + x:DataType="viewModels:SettingsViewModel" + mc:Ignorable="d" + Focusable="True"> + <Design.DataContext> + <viewModels:SettingsViewModel /> + </Design.DataContext> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <ContentPresenter + x:Name="ContentPresenter" + Grid.Row="1" + IsVisible="False" + KeyboardNavigation.IsTabStop="False"/> + <Grid Name="Pages" IsVisible="False" Grid.Row="2"> + <settings:SettingsUiView Name="UiPage" /> + <settings:SettingsInputView Name="InputPage" /> + <settings:SettingsHotkeysView Name="HotkeysPage" /> + <settings:SettingsSystemView Name="SystemPage" /> + <settings:SettingsCPUView Name="CpuPage" /> + <settings:SettingsGraphicsView Name="GraphicsPage" /> + <settings:SettingsAudioView Name="AudioPage" /> + <settings:SettingsNetworkView Name="NetworkPage" /> + <settings:SettingsLoggingView Name="LoggingPage" /> + </Grid> + <ui:NavigationView + Grid.Row="1" + IsSettingsVisible="False" + Name="NavPanel" + IsBackEnabled="False" + PaneDisplayMode="Left" + Margin="2,10,10,0" + VerticalAlignment="Stretch" + HorizontalAlignment="Stretch" + OpenPaneLength="200"> + <ui:NavigationView.MenuItems> + <ui:NavigationViewItem + IsSelected="True" + Content="{locale:Locale SettingsTabGeneral}" + Tag="UiPage" + IconSource="New" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabInput}" + Tag="InputPage" + IconSource="Games" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabHotkeys}" + Tag="HotkeysPage" + IconSource="Keyboard" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabSystem}" + Tag="SystemPage" + IconSource="Settings" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabCpu}" + Tag="CpuPage"> + <ui:NavigationViewItem.IconSource> + <ui:FontIconSource + FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons" + Glyph="{helpers:GlyphValueConverter Chip}" /> + </ui:NavigationViewItem.IconSource> + </ui:NavigationViewItem> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabGraphics}" + Tag="GraphicsPage" + IconSource="Image" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabAudio}" + IconSource="Audio" + Tag="AudioPage" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabNetwork}" + Tag="NetworkPage" + IconSource="Globe" /> + <ui:NavigationViewItem + Content="{locale:Locale SettingsTabLogging}" + Tag="LoggingPage" + IconSource="Document" /> + </ui:NavigationView.MenuItems> + <ui:NavigationView.Styles> + <Style Selector="Grid#PlaceholderGrid"> + <Setter Property="Height" Value="40" /> + </Style> + <Style Selector="ui|NavigationViewItem ui|SymbolIcon"> + <Setter Property="FlowDirection" Value="LeftToRight" /> + </Style> + </ui:NavigationView.Styles> + </ui:NavigationView> + <ReversibleStackPanel + Grid.Row="2" + Margin="10" + Spacing="10" + Orientation="Horizontal" + HorizontalAlignment="Right" + ReverseOrder="{Binding IsMacOS}"> + <Button + HotKey="Enter" + Classes="accent" + Content="{locale:Locale SettingsButtonOk}" + Command="{Binding OkButton}" /> + <Button + HotKey="Escape" + Content="{locale:Locale SettingsButtonCancel}" + Command="{Binding CancelButton}" /> + <Button + Content="{locale:Locale SettingsButtonApply}" + Command="{Binding ApplyButton}" /> + </ReversibleStackPanel> + </Grid> +</window:StyleableWindow> diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs b/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs new file mode 100644 index 00000000..d7bb0b88 --- /dev/null +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs @@ -0,0 +1,103 @@ +using Avalonia.Controls; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using System; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class SettingsWindow : StyleableWindow + { + internal SettingsViewModel ViewModel { get; set; } + + public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager) + { + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}"; + + ViewModel = new SettingsViewModel(virtualFileSystem, contentManager); + DataContext = ViewModel; + + ViewModel.CloseWindow += Close; + ViewModel.SaveSettingsEvent += SaveSettings; + + InitializeComponent(); + Load(); + } + + public SettingsWindow() + { + ViewModel = new SettingsViewModel(); + DataContext = ViewModel; + + InitializeComponent(); + Load(); + } + + public void SaveSettings() + { + InputPage.ControllerSettings?.SaveCurrentProfile(); + + if (Owner is MainWindow window && ViewModel.DirectoryChanged) + { + window.LoadApplications(); + } + } + + private void Load() + { + Pages.Children.Clear(); + NavPanel.SelectionChanged += NavPanelOnSelectionChanged; + NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0); + } + + private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e) + { + if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null) + { + switch (navItem.Tag.ToString()) + { + case "UiPage": + UiPage.ViewModel = ViewModel; + NavPanel.Content = UiPage; + break; + case "InputPage": + NavPanel.Content = InputPage; + break; + case "HotkeysPage": + NavPanel.Content = HotkeysPage; + break; + case "SystemPage": + SystemPage.ViewModel = ViewModel; + NavPanel.Content = SystemPage; + break; + case "CpuPage": + NavPanel.Content = CpuPage; + break; + case "GraphicsPage": + NavPanel.Content = GraphicsPage; + break; + case "AudioPage": + NavPanel.Content = AudioPage; + break; + case "NetworkPage": + NavPanel.Content = NetworkPage; + break; + case "LoggingPage": + NavPanel.Content = LoggingPage; + break; + default: + throw new NotImplementedException(); + } + } + } + + protected override void OnClosing(WindowClosingEventArgs e) + { + HotkeysPage.Dispose(); + InputPage.Dispose(); + base.OnClosing(e); + } + } +} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.cs b/src/Ryujinx/UI/Windows/SettingsWindow.cs deleted file mode 100644 index 270a8ad4..00000000 --- a/src/Ryujinx/UI/Windows/SettingsWindow.cs +++ /dev/null @@ -1,847 +0,0 @@ -using Gtk; -using LibHac.Tools.FsSystem; -using Ryujinx.Audio.Backends.OpenAL; -using Ryujinx.Audio.Backends.SDL2; -using Ryujinx.Audio.Backends.SoundIo; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Multiplayer; -using Ryujinx.Common.GraphicsDriver; -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.Helper; -using Ryujinx.UI.Widgets; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net.NetworkInformation; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using GUI = Gtk.Builder.ObjectAttribute; - -namespace Ryujinx.UI.Windows -{ - public class SettingsWindow : Window - { - private readonly MainWindow _parent; - private readonly ListStore _gameDirsBoxStore; - private readonly ListStore _audioBackendStore; - private readonly TimeZoneContentManager _timeZoneContentManager; - private readonly HashSet<string> _validTzRegions; - - private long _systemTimeOffset; - private float _previousVolumeLevel; - private bool _directoryChanged = false; - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] CheckButton _traceLogToggle; - [GUI] CheckButton _errorLogToggle; - [GUI] CheckButton _warningLogToggle; - [GUI] CheckButton _infoLogToggle; - [GUI] CheckButton _stubLogToggle; - [GUI] CheckButton _debugLogToggle; - [GUI] CheckButton _fileLogToggle; - [GUI] CheckButton _guestLogToggle; - [GUI] CheckButton _fsAccessLogToggle; - [GUI] Adjustment _fsLogSpinAdjustment; - [GUI] ComboBoxText _graphicsDebugLevel; - [GUI] CheckButton _dockedModeToggle; - [GUI] CheckButton _discordToggle; - [GUI] CheckButton _checkUpdatesToggle; - [GUI] CheckButton _showConfirmExitToggle; - [GUI] RadioButton _hideCursorNever; - [GUI] RadioButton _hideCursorOnIdle; - [GUI] RadioButton _hideCursorAlways; - [GUI] CheckButton _vSyncToggle; - [GUI] CheckButton _shaderCacheToggle; - [GUI] CheckButton _textureRecompressionToggle; - [GUI] CheckButton _macroHLEToggle; - [GUI] CheckButton _ptcToggle; - [GUI] CheckButton _internetToggle; - [GUI] CheckButton _fsicToggle; - [GUI] RadioButton _mmSoftware; - [GUI] RadioButton _mmHost; - [GUI] RadioButton _mmHostUnsafe; - [GUI] CheckButton _expandRamToggle; - [GUI] CheckButton _ignoreToggle; - [GUI] CheckButton _directKeyboardAccess; - [GUI] CheckButton _directMouseAccess; - [GUI] ComboBoxText _systemLanguageSelect; - [GUI] ComboBoxText _systemRegionSelect; - [GUI] Entry _systemTimeZoneEntry; - [GUI] EntryCompletion _systemTimeZoneCompletion; - [GUI] Box _audioBackendBox; - [GUI] ComboBox _audioBackendSelect; - [GUI] Label _audioVolumeLabel; - [GUI] Scale _audioVolumeSlider; - [GUI] SpinButton _systemTimeYearSpin; - [GUI] SpinButton _systemTimeMonthSpin; - [GUI] SpinButton _systemTimeDaySpin; - [GUI] SpinButton _systemTimeHourSpin; - [GUI] SpinButton _systemTimeMinuteSpin; - [GUI] Adjustment _systemTimeYearSpinAdjustment; - [GUI] Adjustment _systemTimeMonthSpinAdjustment; - [GUI] Adjustment _systemTimeDaySpinAdjustment; - [GUI] Adjustment _systemTimeHourSpinAdjustment; - [GUI] Adjustment _systemTimeMinuteSpinAdjustment; - [GUI] ComboBoxText _multiLanSelect; - [GUI] ComboBoxText _multiModeSelect; - [GUI] CheckButton _custThemeToggle; - [GUI] Entry _custThemePath; - [GUI] ToggleButton _browseThemePath; - [GUI] Label _custThemePathLabel; - [GUI] TreeView _gameDirsBox; - [GUI] Entry _addGameDirBox; - [GUI] ComboBoxText _galThreading; - [GUI] Entry _graphicsShadersDumpPath; - [GUI] ComboBoxText _anisotropy; - [GUI] ComboBoxText _aspectRatio; - [GUI] ComboBoxText _antiAliasing; - [GUI] ComboBoxText _scalingFilter; - [GUI] ComboBoxText _graphicsBackend; - [GUI] ComboBoxText _preferredGpu; - [GUI] ComboBoxText _resScaleCombo; - [GUI] Entry _resScaleText; - [GUI] Adjustment _scalingFilterLevel; - [GUI] Scale _scalingFilterSlider; - [GUI] ToggleButton _configureController1; - [GUI] ToggleButton _configureController2; - [GUI] ToggleButton _configureController3; - [GUI] ToggleButton _configureController4; - [GUI] ToggleButton _configureController5; - [GUI] ToggleButton _configureController6; - [GUI] ToggleButton _configureController7; - [GUI] ToggleButton _configureController8; - [GUI] ToggleButton _configureControllerH; - -#pragma warning restore CS0649, IDE0044 - - public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } - - private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) - { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - _parent = parent; - - builder.Autoconnect(this); - - _timeZoneContentManager = new TimeZoneContentManager(); - _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, IntegrityCheckLevel.None); - - _validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly. - - // Bind Events. - _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1); - _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2); - _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3); - _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4); - _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5); - _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6); - _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7); - _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8); - _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld); - _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; - - _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; - _scalingFilter.Changed += (sender, args) => _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; - _galThreading.Changed += (sender, args) => - { - if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()) - { - GtkDialog.CreateInfoDialog("Warning - Backend Threading", "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's."); - } - }; - - // Setup Currents. - if (ConfigurationState.Instance.Logger.EnableTrace) - { - _traceLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableFileLog) - { - _fileLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableError) - { - _errorLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableWarn) - { - _warningLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableInfo) - { - _infoLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableStub) - { - _stubLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableDebug) - { - _debugLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableGuest) - { - _guestLogToggle.Click(); - } - - if (ConfigurationState.Instance.Logger.EnableFsAccessLog) - { - _fsAccessLogToggle.Click(); - } - - foreach (GraphicsDebugLevel level in Enum.GetValues<GraphicsDebugLevel>()) - { - _graphicsDebugLevel.Append(level.ToString(), level.ToString()); - } - - _graphicsDebugLevel.SetActiveId(ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value.ToString()); - - if (ConfigurationState.Instance.System.EnableDockedMode) - { - _dockedModeToggle.Click(); - } - - if (ConfigurationState.Instance.EnableDiscordIntegration) - { - _discordToggle.Click(); - } - - if (ConfigurationState.Instance.CheckUpdatesOnStart) - { - _checkUpdatesToggle.Click(); - } - - if (ConfigurationState.Instance.ShowConfirmExit) - { - _showConfirmExitToggle.Click(); - } - - switch (ConfigurationState.Instance.HideCursor.Value) - { - case HideCursorMode.Never: - _hideCursorNever.Click(); - break; - case HideCursorMode.OnIdle: - _hideCursorOnIdle.Click(); - break; - case HideCursorMode.Always: - _hideCursorAlways.Click(); - break; - } - - if (ConfigurationState.Instance.Graphics.EnableVsync) - { - _vSyncToggle.Click(); - } - - if (ConfigurationState.Instance.Graphics.EnableShaderCache) - { - _shaderCacheToggle.Click(); - } - - if (ConfigurationState.Instance.Graphics.EnableTextureRecompression) - { - _textureRecompressionToggle.Click(); - } - - if (ConfigurationState.Instance.Graphics.EnableMacroHLE) - { - _macroHLEToggle.Click(); - } - - if (ConfigurationState.Instance.System.EnablePtc) - { - _ptcToggle.Click(); - } - - if (ConfigurationState.Instance.System.EnableInternetAccess) - { - _internetToggle.Click(); - } - - if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) - { - _fsicToggle.Click(); - } - - switch (ConfigurationState.Instance.System.MemoryManagerMode.Value) - { - case MemoryManagerMode.SoftwarePageTable: - _mmSoftware.Click(); - break; - case MemoryManagerMode.HostMapped: - _mmHost.Click(); - break; - case MemoryManagerMode.HostMappedUnsafe: - _mmHostUnsafe.Click(); - break; - } - - if (ConfigurationState.Instance.System.ExpandRam) - { - _expandRamToggle.Click(); - } - - if (ConfigurationState.Instance.System.IgnoreMissingServices) - { - _ignoreToggle.Click(); - } - - if (ConfigurationState.Instance.Hid.EnableKeyboard) - { - _directKeyboardAccess.Click(); - } - - if (ConfigurationState.Instance.Hid.EnableMouse) - { - _directMouseAccess.Click(); - } - - if (ConfigurationState.Instance.UI.EnableCustomTheme) - { - _custThemeToggle.Click(); - } - - // Custom EntryCompletion Columns. If added to glade, need to override more signals - ListStore tzList = new(typeof(string), typeof(string), typeof(string)); - _systemTimeZoneCompletion.Model = tzList; - - CellRendererText offsetCol = new(); - CellRendererText abbrevCol = new(); - - _systemTimeZoneCompletion.PackStart(offsetCol, false); - _systemTimeZoneCompletion.AddAttribute(offsetCol, "text", 0); - _systemTimeZoneCompletion.TextColumn = 1; // Regions Column - _systemTimeZoneCompletion.PackStart(abbrevCol, false); - _systemTimeZoneCompletion.AddAttribute(abbrevCol, "text", 2); - - int maxLocationLength = 0; - - foreach (var (offset, location, abbr) in _timeZoneContentManager.ParseTzOffsets()) - { - var hours = Math.DivRem(offset, 3600, out int seconds); - var minutes = Math.Abs(seconds) / 60; - - var abbr2 = (abbr.StartsWith('+') || abbr.StartsWith('-')) ? string.Empty : abbr; - - tzList.AppendValues($"UTC{hours:+0#;-0#;+00}:{minutes:D2} ", location, abbr2); - _validTzRegions.Add(location); - - maxLocationLength = Math.Max(maxLocationLength, location.Length); - } - - _systemTimeZoneEntry.WidthChars = Math.Max(20, maxLocationLength + 1); // Ensure minimum Entry width - _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); - - _systemTimeZoneCompletion.MatchFunc = TimeZoneMatchFunc; - - _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); - _systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString()); - _galThreading.SetActiveId(ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()); - _resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString()); - _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); - _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); - _graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString()); - _antiAliasing.SetActiveId(((int)ConfigurationState.Instance.Graphics.AntiAliasing.Value).ToString()); - _scalingFilter.SetActiveId(((int)ConfigurationState.Instance.Graphics.ScalingFilter.Value).ToString()); - - UpdatePreferredGpuComboBox(); - - _graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox(); - PopulateNetworkInterfaces(); - _multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); - _multiModeSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.Mode.Value.ToString()); - - _custThemePath.Buffer.Text = ConfigurationState.Instance.UI.CustomThemePath; - _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); - _scalingFilterLevel.Value = ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value; - _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; - _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; - _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; - _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; - _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; - - _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); - _gameDirsBoxStore = new ListStore(typeof(string)); - _gameDirsBox.Model = _gameDirsBoxStore; - - foreach (string gameDir in ConfigurationState.Instance.UI.GameDirs.Value) - { - _gameDirsBoxStore.AppendValues(gameDir); - } - - if (_custThemeToggle.Active == false) - { - _custThemePath.Sensitive = false; - _custThemePathLabel.Sensitive = false; - _browseThemePath.Sensitive = false; - } - - // Setup system time spinners - UpdateSystemTimeSpinners(); - - _audioBackendStore = new ListStore(typeof(string), typeof(AudioBackend)); - - TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl); - TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo); - TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2); - TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy); - - _audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore); - _audioBackendSelect.EntryTextColumn = 0; - _audioBackendSelect.Entry.IsEditable = false; - - switch (ConfigurationState.Instance.System.AudioBackend.Value) - { - case AudioBackend.OpenAl: - _audioBackendSelect.SetActiveIter(openAlIter); - break; - case AudioBackend.SoundIo: - _audioBackendSelect.SetActiveIter(soundIoIter); - break; - case AudioBackend.SDL2: - _audioBackendSelect.SetActiveIter(sdl2Iter); - break; - case AudioBackend.Dummy: - _audioBackendSelect.SetActiveIter(dummyIter); - break; - default: - throw new InvalidOperationException($"{nameof(ConfigurationState.Instance.System.AudioBackend)} contains an invalid value: {ConfigurationState.Instance.System.AudioBackend.Value}"); - } - - _audioBackendBox.Add(_audioBackendSelect); - _audioBackendSelect.Show(); - - _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume; - _audioVolumeLabel = new Label("Volume: "); - _audioVolumeSlider = new Scale(Orientation.Horizontal, 0, 100, 1); - _audioVolumeLabel.MarginStart = 10; - _audioVolumeSlider.ValuePos = PositionType.Right; - _audioVolumeSlider.WidthRequest = 200; - - _audioVolumeSlider.Value = _previousVolumeLevel * 100; - _audioVolumeSlider.ValueChanged += VolumeSlider_OnChange; - _audioBackendBox.Add(_audioVolumeLabel); - _audioBackendBox.Add(_audioVolumeSlider); - _audioVolumeLabel.Show(); - _audioVolumeSlider.Show(); - - bool openAlIsSupported = false; - bool soundIoIsSupported = false; - bool sdl2IsSupported = false; - - Task.Run(() => - { - openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported; - soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported; - sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported; - }); - - // This function runs whenever the dropdown is opened - _audioBackendSelect.SetCellDataFunc(_audioBackendSelect.Cells[0], (layout, cell, model, iter) => - { - cell.Sensitive = ((AudioBackend)_audioBackendStore.GetValue(iter, 1)) switch - { - AudioBackend.OpenAl => openAlIsSupported, - AudioBackend.SoundIo => soundIoIsSupported, - AudioBackend.SDL2 => sdl2IsSupported, - AudioBackend.Dummy => true, - _ => throw new InvalidOperationException($"{nameof(_audioBackendStore)} contains an invalid value for iteration {iter}: {_audioBackendStore.GetValue(iter, 1)}"), - }; - }); - - if (OperatingSystem.IsMacOS()) - { - var store = (_graphicsBackend.Model as ListStore); - store.GetIter(out TreeIter openglIter, new TreePath(new[] { 1 })); - store.Remove(ref openglIter); - - _graphicsBackend.Model = store; - } - } - - private void UpdatePreferredGpuComboBox() - { - _preferredGpu.RemoveAll(); - - if (Enum.Parse<GraphicsBackend>(_graphicsBackend.ActiveId) == GraphicsBackend.Vulkan) - { - var devices = Graphics.Vulkan.VulkanRenderer.GetPhysicalDevices(); - string preferredGpuIdFromConfig = ConfigurationState.Instance.Graphics.PreferredGpu.Value; - string preferredGpuId = preferredGpuIdFromConfig; - bool noGpuId = string.IsNullOrEmpty(preferredGpuIdFromConfig); - - foreach (var device in devices) - { - string dGpu = device.IsDiscrete ? " (dGPU)" : ""; - _preferredGpu.Append(device.Id, $"{device.Name}{dGpu}"); - - // If there's no GPU selected yet, we just pick the first GPU. - // If there's a discrete GPU available, we always prefer that over the previous selection, - // as it is likely to have better performance and more features. - // If the configuration file already has a GPU selection, we always prefer that instead. - if (noGpuId && (string.IsNullOrEmpty(preferredGpuId) || device.IsDiscrete)) - { - preferredGpuId = device.Id; - } - } - - if (!string.IsNullOrEmpty(preferredGpuId)) - { - _preferredGpu.SetActiveId(preferredGpuId); - } - } - } - - private void PopulateNetworkInterfaces() - { - NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); - - foreach (NetworkInterface nif in interfaces) - { - string guid = nif.Id; - string name = nif.Name; - - _multiLanSelect.Append(guid, name); - } - } - - private void UpdateSystemTimeSpinners() - { - //Bind system time events - _systemTimeYearSpin.ValueChanged -= SystemTimeSpin_ValueChanged; - _systemTimeMonthSpin.ValueChanged -= SystemTimeSpin_ValueChanged; - _systemTimeDaySpin.ValueChanged -= SystemTimeSpin_ValueChanged; - _systemTimeHourSpin.ValueChanged -= SystemTimeSpin_ValueChanged; - _systemTimeMinuteSpin.ValueChanged -= SystemTimeSpin_ValueChanged; - - //Apply actual system time + SystemTimeOffset to system time spin buttons - DateTime systemTime = DateTime.Now.AddSeconds(_systemTimeOffset); - - _systemTimeYearSpinAdjustment.Value = systemTime.Year; - _systemTimeMonthSpinAdjustment.Value = systemTime.Month; - _systemTimeDaySpinAdjustment.Value = systemTime.Day; - _systemTimeHourSpinAdjustment.Value = systemTime.Hour; - _systemTimeMinuteSpinAdjustment.Value = systemTime.Minute; - - //Format spin buttons text to include leading zeros - _systemTimeYearSpin.Text = systemTime.Year.ToString("0000"); - _systemTimeMonthSpin.Text = systemTime.Month.ToString("00"); - _systemTimeDaySpin.Text = systemTime.Day.ToString("00"); - _systemTimeHourSpin.Text = systemTime.Hour.ToString("00"); - _systemTimeMinuteSpin.Text = systemTime.Minute.ToString("00"); - - //Bind system time events - _systemTimeYearSpin.ValueChanged += SystemTimeSpin_ValueChanged; - _systemTimeMonthSpin.ValueChanged += SystemTimeSpin_ValueChanged; - _systemTimeDaySpin.ValueChanged += SystemTimeSpin_ValueChanged; - _systemTimeHourSpin.ValueChanged += SystemTimeSpin_ValueChanged; - _systemTimeMinuteSpin.ValueChanged += SystemTimeSpin_ValueChanged; - } - - private void SaveSettings() - { - if (_directoryChanged) - { - List<string> gameDirs = new(); - - _gameDirsBoxStore.GetIterFirst(out TreeIter treeIter); - - for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++) - { - gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0)); - - _gameDirsBoxStore.IterNext(ref treeIter); - } - - ConfigurationState.Instance.UI.GameDirs.Value = gameDirs; - - _directoryChanged = false; - } - - HideCursorMode hideCursor = HideCursorMode.Never; - - if (_hideCursorOnIdle.Active) - { - hideCursor = HideCursorMode.OnIdle; - } - - if (_hideCursorAlways.Active) - { - hideCursor = HideCursorMode.Always; - } - - if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f) - { - resScaleCustom = 1.0f; - } - - if (_validTzRegions.Contains(_systemTimeZoneEntry.Text)) - { - ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneEntry.Text; - } - - MemoryManagerMode memoryMode = MemoryManagerMode.SoftwarePageTable; - - if (_mmHost.Active) - { - memoryMode = MemoryManagerMode.HostMapped; - } - - if (_mmHostUnsafe.Active) - { - memoryMode = MemoryManagerMode.HostMappedUnsafe; - } - - BackendThreading backendThreading = Enum.Parse<BackendThreading>(_galThreading.ActiveId); - if (ConfigurationState.Instance.Graphics.BackendThreading != backendThreading) - { - DriverUtilities.ToggleOGLThreading(backendThreading == BackendThreading.Off); - } - - ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; - ConfigurationState.Instance.Logger.EnableTrace.Value = _traceLogToggle.Active; - ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; - ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; - ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; - ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; - ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; - ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; - ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; - ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId); - ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; - ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; - ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active; - ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active; - ConfigurationState.Instance.HideCursor.Value = hideCursor; - ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; - ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active; - ConfigurationState.Instance.Graphics.EnableTextureRecompression.Value = _textureRecompressionToggle.Active; - ConfigurationState.Instance.Graphics.EnableMacroHLE.Value = _macroHLEToggle.Active; - ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; - ConfigurationState.Instance.System.EnableInternetAccess.Value = _internetToggle.Active; - ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; - ConfigurationState.Instance.System.MemoryManagerMode.Value = memoryMode; - ConfigurationState.Instance.System.ExpandRam.Value = _expandRamToggle.Active; - ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; - ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; - ConfigurationState.Instance.Hid.EnableMouse.Value = _directMouseAccess.Active; - ConfigurationState.Instance.UI.EnableCustomTheme.Value = _custThemeToggle.Active; - ConfigurationState.Instance.System.Language.Value = Enum.Parse<Language>(_systemLanguageSelect.ActiveId); - ConfigurationState.Instance.System.Region.Value = Enum.Parse<Common.Configuration.System.Region>(_systemRegionSelect.ActiveId); - ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset; - ConfigurationState.Instance.UI.CustomThemePath.Value = _custThemePath.Buffer.Text; - ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; - ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; - ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); - ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse<AspectRatio>(_aspectRatio.ActiveId); - ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = Enum.Parse<GraphicsBackend>(_graphicsBackend.ActiveId); - ConfigurationState.Instance.Graphics.PreferredGpu.Value = _preferredGpu.ActiveId; - ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); - ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; - ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; - ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId); - ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId); - ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; - - _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; - - ConfigurationState.Instance.Multiplayer.Mode.Value = Enum.Parse<MultiplayerMode>(_multiModeSelect.ActiveId); - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; - - if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter)) - { - ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1); - } - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - - _parent.UpdateInternetAccess(); - MainWindow.UpdateGraphicsConfig(); - ThemeHelper.ApplyTheme(); - } - - // - // Events - // - private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e) - { - if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text)) - { - _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); - } - } - - private bool TimeZoneMatchFunc(EntryCompletion compl, string key, TreeIter iter) - { - key = key.Trim().Replace(' ', '_'); - - return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region - ((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr - ((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset - } - - private void SystemTimeSpin_ValueChanged(object sender, EventArgs e) - { - int year = _systemTimeYearSpin.ValueAsInt; - int month = _systemTimeMonthSpin.ValueAsInt; - int day = _systemTimeDaySpin.ValueAsInt; - int hour = _systemTimeHourSpin.ValueAsInt; - int minute = _systemTimeMinuteSpin.ValueAsInt; - - if (!DateTime.TryParse(year + "-" + month + "-" + day + " " + hour + ":" + minute, out DateTime newTime)) - { - UpdateSystemTimeSpinners(); - - return; - } - - newTime = newTime.AddSeconds(DateTime.Now.Second).AddMilliseconds(DateTime.Now.Millisecond); - - long systemTimeOffset = (long)Math.Ceiling((newTime - DateTime.Now).TotalMinutes) * 60L; - - if (_systemTimeOffset != systemTimeOffset) - { - _systemTimeOffset = systemTimeOffset; - UpdateSystemTimeSpinners(); - } - } - - private void AddDir_Pressed(object sender, EventArgs args) - { - if (Directory.Exists(_addGameDirBox.Buffer.Text)) - { - _gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text); - _directoryChanged = true; - } - else - { - FileChooserNative fileChooser = new("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Add", "Cancel") - { - SelectMultiple = true, - }; - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - _directoryChanged = false; - foreach (string directory in fileChooser.Filenames) - { - if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter)) - { - do - { - if (directory.Equals((string)_gameDirsBoxStore.GetValue(treeIter, 0))) - { - break; - } - } while (_gameDirsBoxStore.IterNext(ref treeIter)); - } - - if (!_directoryChanged) - { - _gameDirsBoxStore.AppendValues(directory); - } - } - - _directoryChanged = true; - } - - fileChooser.Dispose(); - } - - _addGameDirBox.Buffer.Text = ""; - - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - } - - private void RemoveDir_Pressed(object sender, EventArgs args) - { - TreeSelection selection = _gameDirsBox.Selection; - - if (selection.GetSelected(out TreeIter treeIter)) - { - _gameDirsBoxStore.Remove(ref treeIter); - - _directoryChanged = true; - } - - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - } - - private void CustThemeToggle_Activated(object sender, EventArgs args) - { - _custThemePath.Sensitive = _custThemeToggle.Active; - _custThemePathLabel.Sensitive = _custThemeToggle.Active; - _browseThemePath.Sensitive = _custThemeToggle.Active; - } - - private void BrowseThemeDir_Pressed(object sender, EventArgs args) - { - using (FileChooserNative fileChooser = new("Choose the theme to load", this, FileChooserAction.Open, "Select", "Cancel")) - { - FileFilter filter = new() - { - Name = "Theme Files", - }; - filter.AddPattern("*.css"); - - fileChooser.AddFilter(filter); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - _custThemePath.Buffer.Text = fileChooser.Filename; - } - } - - _browseThemePath.SetStateFlags(StateFlags.Normal, true); - } - - private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex) - { - ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - - ControllerWindow controllerWindow = new(_parent, playerIndex); - - controllerWindow.SetSizeRequest((int)(controllerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(controllerWindow.DefaultHeight * Program.WindowScaleFactor)); - controllerWindow.Show(); - } - - private void VolumeSlider_OnChange(object sender, EventArgs args) - { - ConfigurationState.Instance.System.AudioVolume.Value = (float)(_audioVolumeSlider.Value / 100); - } - - private void SaveToggle_Activated(object sender, EventArgs args) - { - SaveSettings(); - Dispose(); - } - - private void ApplyToggle_Activated(object sender, EventArgs args) - { - SaveSettings(); - } - - private void CloseToggle_Activated(object sender, EventArgs args) - { - ConfigurationState.Instance.System.AudioVolume.Value = _previousVolumeLevel; - Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.glade b/src/Ryujinx/UI/Windows/SettingsWindow.glade deleted file mode 100644 index f0dbd6b6..00000000 --- a/src/Ryujinx/UI/Windows/SettingsWindow.glade +++ /dev/null @@ -1,3221 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.40.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkAdjustment" id="_fsLogSpinAdjustment"> - <property name="upper">3</property> - <property name="step-increment">1</property> - <property name="page-increment">10</property> - </object> - <object class="GtkAdjustment" id="_scalingFilterLevel"> - <property name="upper">101</property> - <property name="step-increment">1</property> - <property name="page-increment">5</property> - <property name="page-size">1</property> - </object> - <object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment"> - <property name="lower">1</property> - <property name="upper">31</property> - <property name="step-increment">1</property> - <property name="page-increment">5</property> - </object> - <object class="GtkAdjustment" id="_systemTimeHourSpinAdjustment"> - <property name="upper">23</property> - <property name="step-increment">1</property> - <property name="page-increment">5</property> - </object> - <object class="GtkAdjustment" id="_systemTimeMinuteSpinAdjustment"> - <property name="upper">59</property> - <property name="step-increment">1</property> - <property name="page-increment">5</property> - </object> - <object class="GtkAdjustment" id="_systemTimeMonthSpinAdjustment"> - <property name="lower">1</property> - <property name="upper">12</property> - <property name="step-increment">1</property> - <property name="page-increment">5</property> - </object> - <object class="GtkAdjustment" id="_systemTimeYearSpinAdjustment"> - <property name="lower">2000</property> - <property name="upper">2060</property> - <property name="step-increment">1</property> - <property name="page-increment">10</property> - </object> - <object class="GtkEntryCompletion" id="_systemTimeZoneCompletion"> - <property name="minimum-key-length">0</property> - <property name="inline-completion">True</property> - <property name="inline-selection">True</property> - </object> - <object class="GtkWindow" id="_settingsWin"> - <property name="can-focus">False</property> - <property name="title" translatable="yes">Ryujinx - Settings</property> - <property name="modal">True</property> - <property name="window-position">center</property> - <property name="default-width">650</property> - <property name="default-height">650</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="shadow-type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkNotebook"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <child> - <object class="GtkBox" id="TabGeneral"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">10</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CatGeneral"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">General</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="General"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_discordToggle"> - <property name="label" translatable="yes">Enable Discord Rich Presence</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Choose whether or not to display Ryujinx on your "currently playing" Discord activity</property> - <property name="halign">start</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_checkUpdatesToggle"> - <property name="label" translatable="yes">Check for Updates on Launch</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="halign">start</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_showConfirmExitToggle"> - <property name="label" translatable="yes">Show "Confirm Exit" Dialog</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="halign">start</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_hideCursorBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Hide Cursor:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_hideCursorNever"> - <property name="label" translatable="yes">Never</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="active">True</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_hideCursorOnIdle"> - <property name="label" translatable="yes">On Idle</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - <property name="group">_hideCursorNever</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_hideCursorAlways"> - <property name="label" translatable="yes">Always</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - <property name="group">_hideCursorNever</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatGameDir"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Game Directories</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="margin-bottom">10</property> - <property name="shadow-type">in</property> - <child> - <object class="GtkTreeView" id="_gameDirsBox"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="headers-visible">False</property> - <property name="headers-clickable">False</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"/> - </child> - </object> - </child> - <style> - <class name="GameDir"/> - </style> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkEntry" id="_addGameDirBox"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Enter a game directory to add to the list</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_addDir"> - <property name="label" translatable="yes">Add</property> - <property name="width-request">80</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes"> Add a game directory to the list</property> - <property name="margin-left">5</property> - <signal name="toggled" handler="AddDir_Pressed" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_removeDir"> - <property name="label" translatable="yes">Remove</property> - <property name="width-request">80</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes">Remove selected game directory</property> - <property name="margin-left">5</property> - <signal name="toggled" handler="RemoveDir_Pressed" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatThemes"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Themes</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_custThemeToggle"> - <property name="label" translatable="yes">Use Custom Theme</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Enable or disable custom themes in the GUI</property> - <property name="halign">start</property> - <property name="draw-indicator">True</property> - <signal name="toggled" handler="CustThemeToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel" id="_custThemePathLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Path to custom GUI theme</property> - <property name="label" translatable="yes">Custom Theme Path:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_custThemePath"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Path to custom GUI theme</property> - <property name="valign">center</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_browseThemePath"> - <property name="label" translatable="yes">Browse...</property> - <property name="width-request">80</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes">Browse for a custom GUI theme</property> - <property name="margin-left">5</property> - <signal name="toggled" handler="BrowseThemeDir_Pressed" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">10</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">6</property> - </packing> - </child> - </object> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">General</property> - </object> - <packing> - <property name="tab-fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TabInput"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">10</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkCheckButton" id="_dockedModeToggle"> - <property name="label" translatable="yes">Enable Docked Mode</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality. Configure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode. Leave ON if unsure.</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">10</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_directKeyboardAccess"> - <property name="label" translatable="yes">Direct Keyboard Access</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="padding">10</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_directMouseAccess"> - <property name="label" translatable="yes">Direct Mouse Access</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="padding">10</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <!-- n-columns=5 n-rows=5 --> - <object class="GtkGrid" id="ControllerGrid"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">center</property> - <property name="valign">center</property> - <property name="column-spacing">20</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController1"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 3</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController3"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">4</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 2</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController2"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Handheld</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureControllerH"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">4</property> - <property name="top-attach">4</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 6</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController6"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">4</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController5"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 7</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController7"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">4</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 4</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController4"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - <property name="label" translatable="yes">Player 8</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="_configureController8"> - <property name="label" translatable="yes">Configure</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="margin-left">20</property> - <property name="margin-right">20</property> - <property name="margin-top">20</property> - <property name="margin-bottom">20</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">4</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">3</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">3</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">3</property> - <property name="top-attach">4</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">4</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">3</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">3</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">4</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="left-attach">4</property> - <property name="top-attach">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Input</property> - </object> - <packing> - <property name="position">1</property> - <property name="tab-fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TabSystem"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">10</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CatCore"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Core</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="RegionBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System Region</property> - <property name="halign">end</property> - <property name="label" translatable="yes">System Region:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_systemRegionSelect"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System Region</property> - <property name="margin-left">5</property> - <items> - <item id="Japan" translatable="yes">Japan</item> - <item id="USA" translatable="yes">USA</item> - <item id="Europe" translatable="yes">Europe</item> - <item id="Australia" translatable="yes">Australia</item> - <item id="China" translatable="yes">China</item> - <item id="Korea" translatable="yes">Korea</item> - <item id="Taiwan" translatable="yes">Taiwan</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="LanguageBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System Language</property> - <property name="halign">end</property> - <property name="label" translatable="yes">System Language:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_systemLanguageSelect"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System Language</property> - <items> - <item id="AmericanEnglish" translatable="yes">American English</item> - <item id="BritishEnglish" translatable="yes">British English</item> - <item id="CanadianFrench" translatable="yes">Canadian French</item> - <item id="Chinese" translatable="yes">Chinese</item> - <item id="Dutch" translatable="yes">Dutch</item> - <item id="French" translatable="yes">French</item> - <item id="German" translatable="yes">German</item> - <item id="Italian" translatable="yes">Italian</item> - <item id="Japanese" translatable="yes">Japanese</item> - <item id="Korean" translatable="yes">Korean</item> - <item id="LatinAmericanSpanish" translatable="yes">Latin American Spanish</item> - <item id="Portuguese" translatable="yes">Portuguese</item> - <item id="Russian" translatable="yes">Russian</item> - <item id="SimplifiedChinese" translatable="yes">Simplified Chinese</item> - <item id="Spanish" translatable="yes">Spanish</item> - <item id="Taiwanese" translatable="yes">Taiwanese</item> - <item id="TraditionalChinese" translatable="yes">Traditional Chinese</item> - <item id="BrazilianPortuguese" translatable="yes">Brazilian Portuguese</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TimeZoneBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System TimeZone</property> - <property name="halign">end</property> - <property name="label" translatable="yes">System TimeZone:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_systemTimeZoneEntry"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Change System TimeZone</property> - <property name="margin-left">5</property> - <property name="completion">_systemTimeZoneCompletion</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TimeBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change System Time</property> - <property name="halign">end</property> - <property name="label" translatable="yes">System Time:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_systemTimeYearSpin"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="text" translatable="yes">2000</property> - <property name="orientation">vertical</property> - <property name="adjustment">_systemTimeYearSpinAdjustment</property> - <property name="wrap">True</property> - <property name="value">2000</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">end</property> - <property name="label">-</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_systemTimeMonthSpin"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="text" translatable="yes">1</property> - <property name="orientation">vertical</property> - <property name="adjustment">_systemTimeMonthSpinAdjustment</property> - <property name="wrap">True</property> - <property name="value">1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">end</property> - <property name="label">-</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_systemTimeDaySpin"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="text" translatable="yes">1</property> - <property name="orientation">vertical</property> - <property name="adjustment">_systemTimeDaySpinAdjustment</property> - <property name="wrap">True</property> - <property name="value">1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_systemTimeHourSpin"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="text" translatable="yes">0</property> - <property name="orientation">vertical</property> - <property name="adjustment">_systemTimeHourSpinAdjustment</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">end</property> - <property name="label">:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">7</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="_systemTimeMinuteSpin"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="text" translatable="yes">0</property> - <property name="orientation">vertical</property> - <property name="adjustment">_systemTimeMinuteSpinAdjustment</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">8</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_vSyncToggle"> - <property name="label" translatable="yes">Enable VSync</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck. Can be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it. Leave ON if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_ptcToggle"> - <property name="label" translatable="yes">Enable PPTC (Profiled Persistent Translation Cache)</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Saves translated JIT functions so that they do not need to be translated every time the game loads. Reduces stuttering and significantly speeds up boot times after the first boot of a game. Leave ON if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_internetToggle"> - <property name="label" translatable="yes">Enable Guest Internet Access</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Allows the emulated application to connect to the Internet. Games with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well. Does NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet. Leave OFF if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">7</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_fsicToggle"> - <property name="label" translatable="yes">Enable FS Integrity Checks</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log. Has no impact on performance and is meant to help troubleshooting. Leave ON if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">8</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_audioBackendBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <placeholder/> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Changes the backend used to render audio. SDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound. Set to SDL2 if unsure.</property> - <property name="halign">end</property> - <property name="margin-right">5</property> - <property name="label" translatable="yes">Audio Backend: </property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox" id="_memoryManagerBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <placeholder/> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance. Set to HOST UNCHECKED if unsure.</property> - <property name="halign">end</property> - <property name="margin-right">5</property> - <property name="label" translatable="yes">Memory Manager Mode: </property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_mmSoftware"> - <property name="label" translatable="yes">Software</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Use a software page table for address translation. Highest accuracy but slowest performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_mmHost"> - <property name="label" translatable="yes">Host (fast)</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Directly map memory in the host address space. Much faster JIT compilation and execution.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - <property name="group">_mmSoftware</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="_mmHostUnsafe"> - <property name="label" translatable="yes">Host Unchecked (fastest, unsafe)</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - <property name="group">_mmSoftware</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatHacks"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Hacks</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes"> (may cause instability)</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_expandRamToggle"> - <property name="label" translatable="yes">Use alternative memory layout (Developers)</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Utilizes an alternative MemoryMode layout to mimic a Switch development model. This is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance. Leave OFF if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_ignoreToggle"> - <property name="label" translatable="yes">Ignore Missing Services</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games. Leave OFF if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="position">2</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">System</property> - </object> - <packing> - <property name="position">2</property> - <property name="tab-fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TabGraphics"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CatFeatures"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Features</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="FeaturesOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure.</property> - <property name="label" translatable="yes">Graphics Backend Multithreading:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_galThreading"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure.</property> - <property name="active-id">-1</property> - <items> - <item id="Auto" translatable="yes">Auto</item> - <item id="Off" translatable="yes">Off</item> - <item id="On" translatable="yes">On</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Graphics Backend to use</property> - <property name="label" translatable="yes">Graphics Backend:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_graphicsBackend"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Graphics Backend to use</property> - <property name="active-id">-1</property> - <items> - <item id="0" translatable="yes">Vulkan</item> - <item id="1" translatable="yes">OpenGL</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Preferred GPU (Vulkan only)</property> - <property name="label" translatable="yes">Preferred GPU:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_preferredGpu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Preferred GPU (Vulkan only)</property> - <property name="active-id">-1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatEnhancements"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Enhancements</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="EnhancementOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_shaderCacheToggle"> - <property name="label" translatable="yes">Enable Shader Cache</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Saves a disk shader cache which reduces stuttering in subsequent runs. Leave ON if unsure.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_textureRecompressionToggle"> - <property name="label" translatable="yes">Enable Texture Recompression</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Enables or disables Texture Recompression. Reduces VRAM usage at the cost of texture quality, and may also increase stuttering</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_macroHLEToggle"> - <property name="label" translatable="yes">Enable Macro HLE</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Enables or disables high-level emulation of Macro code. Improves performance but may cause graphical glitches in some games</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Resolution Scale applied to applicable render targets.</property> - <property name="label" translatable="yes">Resolution Scale:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_resScaleCombo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Resolution Scale applied to applicable render targets.</property> - <property name="active-id">1</property> - <items> - <item id="1" translatable="yes">Native (720p/1080p)</item> - <item id="2" translatable="yes">2x (1440p/2160p)</item> - <item id="3" translatable="yes">3x (2160p/3240p)</item> - <item id="4" translatable="yes">4x (2880p/4320p)</item> - <item id="-1" translatable="yes">Custom (not recommended)</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_resScaleText"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.</property> - <property name="valign">center</property> - <property name="caps-lock-warning">False</property> - <property name="placeholder-text">1.0</property> - <property name="input-purpose">number</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Applies a final effect to the game render</property> - <property name="label" translatable="yes">Post Processing Effect:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_antiAliasing"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Applies anti-aliasing to the game render</property> - <property name="active-id">1</property> - <items> - <item id="0" translatable="yes">None</item> - <item id="1" translatable="yes">FXAA</item> - <item id="2" translatable="yes">SMAA Low</item> - <item id="3" translatable="yes">SMAA Medium</item> - <item id="4" translatable="yes">SMAA High</item> - <item id="5" translatable="yes">SMAA Ultra</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="width-request">100</property> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property> - <property name="label" translatable="yes">Upscale: </property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_scalingFilter"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property> - <property name="active-id">1</property> - <items> - <item id="0" translatable="yes">Bilinear</item> - <item id="1" translatable="yes">Nearest</item> - <item id="2" translatable="yes">FSR</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkScale" id="_scalingFilterSlider"> - <property name="width-request">200</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="margin-start">5</property> - <property name="adjustment">_scalingFilterLevel</property> - <property name="round-digits">1</property> - <property name="value-pos">right</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Level of Anisotropic Filtering (set to Auto to use the value requested by the game)</property> - <property name="label" translatable="yes">Anisotropic Filtering:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_anisotropy"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Level of Anisotropic Filtering (set to Auto to use the value requested by the game)</property> - <property name="active-id">-1</property> - <items> - <item id="-1" translatable="yes">Auto</item> - <item id="2" translatable="yes">2x</item> - <item id="4" translatable="yes">4x</item> - <item id="8" translatable="yes">8x</item> - <item id="16" translatable="yes">16x</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Aspect Ratio applied to the renderer window.</property> - <property name="label" translatable="yes">Aspect Ratio:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_aspectRatio"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Aspect Ratio applied to the renderer window.</property> - <property name="active-id">1</property> - <items> - <item id="0" translatable="yes">4:3</item> - <item id="1" translatable="yes">16:9</item> - <item id="2" translatable="yes">16:10</item> - <item id="3" translatable="yes">21:9</item> - <item id="4" translatable="yes">32:9</item> - <item id="5" translatable="yes">Stretch to Fit Window</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">7</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatDev"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Developer Options</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="DevOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Graphics Shaders Dump Path</property> - <property name="label" translatable="yes">Graphics Shaders Dump Path:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="_graphicsShadersDumpPath"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Graphics Shaders Dump Path</property> - <property name="valign">center</property> - <property name="caps-lock-warning">False</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="position">3</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Graphics</property> - </object> - <packing> - <property name="position">3</property> - <property name="tab-fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TabLogging"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">10</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CatLogging"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Logging</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="LogggingOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkCheckButton" id="_fileLogToggle"> - <property name="label" translatable="yes">Enable Logging to File</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Saves console logging to a log file on disk. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_stubLogToggle"> - <property name="label" translatable="yes">Enable Stub Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints stub log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_infoLogToggle"> - <property name="label" translatable="yes">Enable Info Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints info log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_warningLogToggle"> - <property name="label" translatable="yes">Enable Warning Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints warning log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_errorLogToggle"> - <property name="label" translatable="yes">Enable Error Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints error log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">6</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_guestLogToggle"> - <property name="label" translatable="yes">Enable Guest Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints guest log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">7</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_fsAccessLogToggle"> - <property name="label" translatable="yes">Enable Fs Access Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">8</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> - <property name="label" translatable="yes">Fs Global Access Log Mode:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinButton"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="tooltip-text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property> - <property name="text" translatable="yes">0</property> - <property name="adjustment">_fsLogSpinAdjustment</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">9</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatDevLogging"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="margin-top">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Use with care</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Developer Options (WARNING: Will reduce performance)</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="DevLoggingOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-top">5</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Requires appropriate log levels enabled.</property> - <property name="label" translatable="yes">Graphics Backend Log Level</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">22</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_graphicsDebugLevel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Requires appropriate log levels enabled.</property> - <property name="margin-left">5</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">22</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_debugLogToggle"> - <property name="label" translatable="yes">Enable Debug Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints debug log messages in the console. Only use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">21</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="_traceLogToggle"> - <property name="label" translatable="yes">Enable Trace Logs</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="tooltip-text" translatable="yes">Prints trace log messages in the console. Does not affect performance.</property> - <property name="halign">start</property> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> - <property name="draw-indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">22</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">22</property> - </packing> - </child> - </object> - <packing> - <property name="position">4</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Logging</property> - </object> - <packing> - <property name="position">4</property> - <property name="tab-fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox" id="TabMultiplayer"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-left">5</property> - <property name="margin-right">10</property> - <property name="margin-top">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="CatMultiplayer"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">Multiplayer</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="MultiplayerOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="ModeBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Mode:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_multiModeSelect"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property> - <property name="active-id">Disabled</property> - <items> - <item id="Disabled" translatable="yes">Disabled</item> - <item id="LdnMitm" translatable="yes">ldn_mitm</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="CatLAN"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">5</property> - <property name="margin-right">5</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">LAN Mode</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="LANOptions"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">start</property> - <property name="margin-left">10</property> - <property name="margin-right">10</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="NetworkInterfaceBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Network Interface:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBoxText" id="_multiLanSelect"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property> - <property name="active-id">0</property> - <items> - <item id="0" translatable="yes">Default</item> - </items> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-bottom">5</property> - <property name="label" translatable="yes">To use LAN functionality in games, Enable Guest Internet Access must be checked in System.</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="padding">5</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="position">5</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Multiplayer</property> - </object> - <packing> - <property name="position">5</property> - <property name="tab-fill">False</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButtonBox" id="_buttonBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-right">5</property> - <property name="margin-top">3</property> - <property name="margin-bottom">3</property> - <property name="spacing">5</property> - <property name="layout-style">end</property> - <child> - <object class="GtkToggleButton" id="SaveToggle"> - <property name="label" translatable="yes">Save</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <signal name="toggled" handler="SaveToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="CloseToggle"> - <property name="label" translatable="yes">Close</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <signal name="toggled" handler="CloseToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton"> - <property name="label" translatable="yes">Apply</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <signal name="clicked" handler="ApplyToggle_Activated" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Windows/StyleableWindow.cs b/src/Ryujinx/UI/Windows/StyleableWindow.cs new file mode 100644 index 00000000..a12d2b3e --- /dev/null +++ b/src/Ryujinx/UI/Windows/StyleableWindow.cs @@ -0,0 +1,44 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.UI.Common.Configuration; +using System.IO; +using System.Reflection; + +namespace Ryujinx.Ava.UI.Windows +{ + public class StyleableWindow : Window + { + public Bitmap IconImage { get; set; } + + public StyleableWindow() + { + WindowStartupLocation = WindowStartupLocation.CenterOwner; + TransparencyLevelHint = new[] { WindowTransparencyLevel.None }; + + using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + Icon = new WindowIcon(stream); + stream.Position = 0; + IconImage = new Bitmap(stream); + + LocaleManager.Instance.LocaleChanged += LocaleChanged; + LocaleChanged(); + } + + private void LocaleChanged() + { + FlowDirection = LocaleManager.Instance.IsRTL() ? FlowDirection.RightToLeft : FlowDirection.LeftToRight; + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; + } + } +} diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml new file mode 100644 index 00000000..3eff389f --- /dev/null +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml @@ -0,0 +1,133 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + Width="500" + Height="300" + mc:Ignorable="d" + x:DataType="viewModels:TitleUpdateViewModel" + Focusable="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Border + Grid.Row="0" + Margin="0 0 0 24" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource AppListHoverBackgroundColor}" + BorderThickness="1" + CornerRadius="5" + Padding="2.5"> + <ListBox + Background="Transparent" + SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}" + ItemsSource="{Binding Views}"> + <ListBox.DataTemplates> + <DataTemplate + DataType="models:TitleUpdateModel"> + <Panel Margin="10"> + <TextBlock + HorizontalAlignment="Left" + VerticalAlignment="Center" + TextWrapping="Wrap" + Text="{Binding Label}" /> + <StackPanel + Spacing="10" + Orientation="Horizontal" + HorizontalAlignment="Right"> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="OpenLocation"> + <ui:SymbolIcon + Symbol="OpenFolder" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + <Button + VerticalAlignment="Center" + HorizontalAlignment="Right" + Padding="10" + MinWidth="0" + MinHeight="0" + Click="RemoveUpdate"> + <ui:SymbolIcon + Symbol="Cancel" + HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + </StackPanel> + </Panel> + </DataTemplate> + <DataTemplate + DataType="viewModels:BaseModel"> + <Panel + Height="33" + Margin="10"> + <TextBlock + HorizontalAlignment="Left" + VerticalAlignment="Center" + TextWrapping="Wrap" + Text="{locale:Locale NoUpdate}" /> + </Panel> + </DataTemplate> + </ListBox.DataTemplates> + <ListBox.Styles> + <Style Selector="ListBoxItem"> + <Setter Property="Background" Value="Transparent" /> + </Style> + </ListBox.Styles> + </ListBox> + </Border> + <Panel + Grid.Row="1" + HorizontalAlignment="Stretch"> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Left"> + <Button + Name="AddButton" + MinWidth="90" + Command="{Binding Add}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveAllButton" + MinWidth="90" + Click="RemoveAll"> + <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> + </Button> + </StackPanel> + <StackPanel + Orientation="Horizontal" + Spacing="10" + HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Click="Save"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Click="Close"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </StackPanel> + </Panel> + </Grid> +</UserControl> diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs new file mode 100644 index 00000000..f3ac6960 --- /dev/null +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -0,0 +1,96 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +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 System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class TitleUpdateWindow : UserControl + { + public TitleUpdateViewModel ViewModel; + + public TitleUpdateWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId) + { + DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId); + + InitializeComponent(); + } + + public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new TitleUpdateWindow(virtualFileSystem, titleId), + Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")), + }; + + Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await ContentDialogHelper.ShowAsync(contentDialog); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + public void Save(object sender, RoutedEventArgs e) + { + ViewModel.Save(); + + if (VisualRoot is MainWindow window) + { + window.LoadApplications(); + } + + ((ContentDialog)Parent).Hide(); + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is TitleUpdateModel model) + { + OpenHelper.LocateFile(model.Path); + } + } + } + + private void RemoveUpdate(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext); + } + } + + private void RemoveAll(object sender, RoutedEventArgs e) + { + ViewModel.TitleUpdates.Clear(); + + ViewModel.SortUpdates(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs deleted file mode 100644 index 1df92933..00000000 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs +++ /dev/null @@ -1,206 +0,0 @@ -using Gtk; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Ns; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.FileSystem; -using Ryujinx.UI.App.Common; -using Ryujinx.UI.Widgets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using GUI = Gtk.Builder.ObjectAttribute; -using SpanHelpers = LibHac.Common.SpanHelpers; - -namespace Ryujinx.UI.Windows -{ - public class TitleUpdateWindow : Window - { - private readonly MainWindow _parent; - private readonly VirtualFileSystem _virtualFileSystem; - private readonly string _titleId; - private readonly string _updateJsonPath; - - private TitleUpdateMetadata _titleUpdateWindowData; - - private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary; - private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - -#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier - [GUI] Label _baseTitleInfoLabel; - [GUI] Box _availableUpdatesBox; - [GUI] RadioButton _noUpdateRadioButton; -#pragma warning restore CS0649, IDE0044 - - public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } - - private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) - { - _parent = parent; - - builder.Autoconnect(this); - - _titleId = titleId; - _virtualFileSystem = virtualFileSystem; - _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json"); - _radioButtonToPathDictionary = new Dictionary<RadioButton, string>(); - - try - { - _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, _serializerContext.TitleUpdateMetadata); - } - catch - { - _titleUpdateWindowData = new TitleUpdateMetadata - { - Selected = "", - Paths = new List<string>(), - }; - } - - _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]"; - - foreach (string path in _titleUpdateWindowData.Paths) - { - AddUpdate(path); - } - - if (_titleUpdateWindowData.Selected == "") - { - _noUpdateRadioButton.Active = true; - } - else - { - foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected)) - { - update.Active = true; - } - } - } - - private void AddUpdate(string path) - { - if (File.Exists(path)) - { - using FileStream file = new(path, FileMode.Open, FileAccess.Read); - - PartitionFileSystem nsp = new(); - nsp.Initialize(file.AsStorage()).ThrowIfFailure(); - - try - { - (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0); - - if (controlNca != null && patchNca != null) - { - ApplicationControlProperty controlData = new(); - - using var nacpFile = new UniqueRef<IFile>(); - - controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); - - RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}"); - radioButton.JoinGroup(_noUpdateRadioButton); - - _availableUpdatesBox.Add(radioButton); - _radioButtonToPathDictionary.Add(radioButton, path); - - radioButton.Show(); - radioButton.Active = true; - } - else - { - GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!"); - } - } - catch (Exception exception) - { - GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}"); - } - } - } - - private void RemoveUpdates(bool removeSelectedOnly = false) - { - foreach (RadioButton radioButton in _noUpdateRadioButton.Group) - { - if (radioButton.Label != "No Update" && (!removeSelectedOnly || radioButton.Active)) - { - _availableUpdatesBox.Remove(radioButton); - _radioButtonToPathDictionary.Remove(radioButton); - radioButton.Dispose(); - } - } - } - - private void AddButton_Clicked(object sender, EventArgs args) - { - using FileChooserNative fileChooser = new("Select update files", this, FileChooserAction.Open, "Add", "Cancel"); - - fileChooser.SelectMultiple = true; - - FileFilter filter = new() - { - Name = "Switch Game Updates", - }; - filter.AddPattern("*.nsp"); - - fileChooser.AddFilter(filter); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - foreach (string path in fileChooser.Filenames) - { - AddUpdate(path); - } - } - } - - private void RemoveButton_Clicked(object sender, EventArgs args) - { - RemoveUpdates(true); - } - - private void RemoveAllButton_Clicked(object sender, EventArgs args) - { - RemoveUpdates(); - } - - private void SaveButton_Clicked(object sender, EventArgs args) - { - _titleUpdateWindowData.Paths.Clear(); - _titleUpdateWindowData.Selected = ""; - - foreach (string paths in _radioButtonToPathDictionary.Values) - { - _titleUpdateWindowData.Paths.Add(paths); - } - - foreach (RadioButton radioButton in _noUpdateRadioButton.Group) - { - if (radioButton.Active) - { - _titleUpdateWindowData.Selected = _radioButtonToPathDictionary.TryGetValue(radioButton, out string updatePath) ? updatePath : ""; - } - } - - JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _serializerContext.TitleUpdateMetadata); - - _parent.UpdateGameTable(); - - Dispose(); - } - - private void CancelButton_Clicked(object sender, EventArgs args) - { - Dispose(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.glade b/src/Ryujinx/UI/Windows/TitleUpdateWindow.glade deleted file mode 100644 index cfbac86d..00000000 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.glade +++ /dev/null @@ -1,214 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkWindow" id="_titleUpdateWindow"> - <property name="can_focus">False</property> - <property name="title" translatable="yes">Ryujinx - Title Update Manager</property> - <property name="modal">True</property> - <property name="window_position">center</property> - <property name="default_width">550</property> - <property name="default_height">250</property> - <child> - <object class="GtkBox" id="MainBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox" id="UpdatesBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel" id="_baseTitleInfoLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="label" translatable="yes">Available Updates</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">10</property> - <property name="margin_right">10</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkBox" id="_availableUpdatesBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkRadioButton" id="_noUpdateRadioButton"> - <property name="label" translatable="yes">No Update</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="active">True</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="layout_style">start</property> - <child> - <object class="GtkButton" id="_addUpdate"> - <property name="label" translatable="yes">Add</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Adds an update to this list</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="AddButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_removeUpdate"> - <property name="label" translatable="yes">Remove</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Removes the selected update</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_removeAllButton"> - <property name="label" translatable="yes">Remove All</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Removes all the updates</property> - <property name="margin_left">10</property> - <signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="margin_bottom">10</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="_saveButton"> - <property name="label" translatable="yes">Save</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="SaveButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="_cancelButton"> - <property name="label" translatable="yes">Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="margin_right">10</property> - <property name="margin_top">2</property> - <property name="margin_bottom">2</property> - <signal name="clicked" handler="CancelButton_Clicked" swapped="no"/> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <child type="titlebar"> - <placeholder/> - </child> - </object> -</interface> diff --git a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs deleted file mode 100644 index bc5a18f9..00000000 --- a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs +++ /dev/null @@ -1,255 +0,0 @@ -using Gtk; -using Pango; -using System; - -namespace Ryujinx.UI.Windows -{ - public partial class UserProfilesManagerWindow : Window - { - private Box _mainBox; - private Label _selectedLabel; - private Box _selectedUserBox; - private Image _selectedUserImage; - private Box _selectedUserInfoBox; - private Entry _selectedUserNameEntry; - private Label _selectedUserIdLabel; - private Box _selectedUserButtonsBox; - private Button _saveProfileNameButton; - private Button _changeProfileImageButton; - private Box _usersTreeViewBox; - private Label _availableUsersLabel; - private ScrolledWindow _usersTreeViewWindow; - private ListStore _tableStore; - private TreeView _usersTreeView; - private Box _bottomBox; - private Button _addButton; - private Button _deleteButton; - private Button _closeButton; - - private void InitializeComponent() - { - // - // UserProfilesManagerWindow - // - CanFocus = false; - Resizable = false; - Modal = true; - WindowPosition = WindowPosition.Center; - DefaultWidth = 620; - DefaultHeight = 548; - TypeHint = Gdk.WindowTypeHint.Dialog; - - // - // _mainBox - // - _mainBox = new Box(Orientation.Vertical, 0); - - // - // _selectedLabel - // - _selectedLabel = new Label("Selected User Profile:") - { - Margin = 15, - Attributes = new AttrList(), - Halign = Align.Start, - }; - _selectedLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); - - // - // _viewBox - // - _usersTreeViewBox = new Box(Orientation.Vertical, 0); - - // - // _SelectedUserBox - // - _selectedUserBox = new Box(Orientation.Horizontal, 0) - { - MarginStart = 30, - }; - - // - // _selectedUserImage - // - _selectedUserImage = new Image(); - - // - // _selectedUserInfoBox - // - _selectedUserInfoBox = new Box(Orientation.Vertical, 0) - { - Homogeneous = true, - }; - - // - // _selectedUserNameEntry - // - _selectedUserNameEntry = new Entry("") - { - MarginStart = 15, - MaxLength = (int)MaxProfileNameLength, - }; - _selectedUserNameEntry.KeyReleaseEvent += SelectedUserNameEntry_KeyReleaseEvent; - - // - // _selectedUserIdLabel - // - _selectedUserIdLabel = new Label("") - { - MarginTop = 15, - MarginStart = 15, - }; - - // - // _selectedUserButtonsBox - // - _selectedUserButtonsBox = new Box(Orientation.Vertical, 0) - { - MarginEnd = 30, - }; - - // - // _saveProfileNameButton - // - _saveProfileNameButton = new Button() - { - Label = "Save Profile Name", - CanFocus = true, - ReceivesDefault = true, - Sensitive = false, - }; - _saveProfileNameButton.Clicked += EditProfileNameButton_Pressed; - - // - // _changeProfileImageButton - // - _changeProfileImageButton = new Button() - { - Label = "Change Profile Image", - CanFocus = true, - ReceivesDefault = true, - MarginTop = 10, - }; - _changeProfileImageButton.Clicked += ChangeProfileImageButton_Pressed; - - // - // _availableUsersLabel - // - _availableUsersLabel = new Label("Available User Profiles:") - { - Margin = 15, - Attributes = new AttrList(), - Halign = Align.Start, - }; - _availableUsersLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); - - // - // _usersTreeViewWindow - // - _usersTreeViewWindow = new ScrolledWindow() - { - ShadowType = ShadowType.In, - CanFocus = true, - Expand = true, - MarginStart = 30, - MarginEnd = 30, - MarginBottom = 15, - }; - - // - // _tableStore - // - _tableStore = new ListStore(typeof(bool), typeof(Gdk.Pixbuf), typeof(string), typeof(Gdk.RGBA)); - - // - // _usersTreeView - // - _usersTreeView = new TreeView(_tableStore) - { - HoverSelection = true, - HeadersVisible = false, - }; - _usersTreeView.RowActivated += UsersTreeView_Activated; - - // - // _bottomBox - // - _bottomBox = new Box(Orientation.Horizontal, 0) - { - MarginStart = 30, - MarginEnd = 30, - MarginBottom = 15, - }; - - // - // _addButton - // - _addButton = new Button() - { - Label = "Add New Profile", - CanFocus = true, - ReceivesDefault = true, - HeightRequest = 35, - }; - _addButton.Clicked += AddButton_Pressed; - - // - // _deleteButton - // - _deleteButton = new Button() - { - Label = "Delete Selected Profile", - CanFocus = true, - ReceivesDefault = true, - HeightRequest = 35, - MarginStart = 10, - }; - _deleteButton.Clicked += DeleteButton_Pressed; - - // - // _closeButton - // - _closeButton = new Button() - { - Label = "Close", - CanFocus = true, - ReceivesDefault = true, - HeightRequest = 35, - WidthRequest = 80, - }; - _closeButton.Clicked += CloseButton_Pressed; - - ShowComponent(); - } - - private void ShowComponent() - { - _usersTreeViewWindow.Add(_usersTreeView); - - _usersTreeViewBox.Add(_usersTreeViewWindow); - _bottomBox.PackStart(_addButton, false, false, 0); - _bottomBox.PackStart(_deleteButton, false, false, 0); - _bottomBox.PackEnd(_closeButton, false, false, 0); - - _selectedUserInfoBox.Add(_selectedUserNameEntry); - _selectedUserInfoBox.Add(_selectedUserIdLabel); - - _selectedUserButtonsBox.Add(_saveProfileNameButton); - _selectedUserButtonsBox.Add(_changeProfileImageButton); - - _selectedUserBox.Add(_selectedUserImage); - _selectedUserBox.PackStart(_selectedUserInfoBox, false, false, 0); - _selectedUserBox.PackEnd(_selectedUserButtonsBox, false, false, 0); - - _mainBox.PackStart(_selectedLabel, false, false, 0); - _mainBox.PackStart(_selectedUserBox, false, true, 0); - _mainBox.PackStart(_availableUsersLabel, false, false, 0); - _mainBox.Add(_usersTreeViewBox); - _mainBox.Add(_bottomBox); - - Add(_mainBox); - - ShowAll(); - } - } -} diff --git a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs deleted file mode 100644 index d1e5fa9f..00000000 --- a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs +++ /dev/null @@ -1,328 +0,0 @@ -using Gtk; -using Ryujinx.Common.Memory; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; - -namespace Ryujinx.UI.Windows -{ - public partial class UserProfilesManagerWindow : Window - { - private const uint MaxProfileNameLength = 0x20; - - private readonly AccountManager _accountManager; - private readonly ContentManager _contentManager; - - private byte[] _bufferImageProfile; - private string _tempNewProfileName; - - private Gdk.RGBA _selectedColor; - - private readonly ManualResetEvent _avatarsPreloadingEvent = new(false); - - public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles") - { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); - - InitializeComponent(); - - _selectedColor.Red = 0.212; - _selectedColor.Green = 0.843; - _selectedColor.Blue = 0.718; - _selectedColor.Alpha = 1; - - _accountManager = accountManager; - _contentManager = contentManager; - - CellRendererToggle userSelectedToggle = new(); - userSelectedToggle.Toggled += UserSelectedToggle_Toggled; - - // NOTE: Uncomment following line when multiple selection of user profiles is supported. - //_usersTreeView.AppendColumn("Selected", userSelectedToggle, "active", 0); - _usersTreeView.AppendColumn("User Icon", new CellRendererPixbuf(), "pixbuf", 1); - _usersTreeView.AppendColumn("User Info", new CellRendererText(), "text", 2, "background-rgba", 3); - - _tableStore.SetSortColumnId(0, SortType.Descending); - - RefreshList(); - - if (_contentManager.GetCurrentFirmwareVersion() != null) - { - Task.Run(() => - { - AvatarWindow.PreloadAvatars(contentManager, virtualFileSystem); - _avatarsPreloadingEvent.Set(); - }); - } - } - - public void RefreshList() - { - _tableStore.Clear(); - - foreach (UserProfile userProfile in _accountManager.GetAllUsers()) - { - _tableStore.AppendValues(userProfile.AccountState == AccountState.Open, new Gdk.Pixbuf(userProfile.Image, 96, 96), $"{userProfile.Name}\n{userProfile.UserId}", Gdk.RGBA.Zero); - - if (userProfile.AccountState == AccountState.Open) - { - _selectedUserImage.Pixbuf = new Gdk.Pixbuf(userProfile.Image, 96, 96); - _selectedUserIdLabel.Text = userProfile.UserId.ToString(); - _selectedUserNameEntry.Text = userProfile.Name; - - _deleteButton.Sensitive = userProfile.UserId != AccountManager.DefaultUserId; - - _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); - _tableStore.SetValue(firstIter, 3, _selectedColor); - } - } - } - - // - // Events - // - - private void UsersTreeView_Activated(object o, RowActivatedArgs args) - { - SelectUserTreeView(); - } - - private void UserSelectedToggle_Toggled(object o, ToggledArgs args) - { - SelectUserTreeView(); - } - - private void SelectUserTreeView() - { - // Get selected item informations. - _usersTreeView.Selection.GetSelected(out TreeIter selectedIter); - - Gdk.Pixbuf userPicture = (Gdk.Pixbuf)_tableStore.GetValue(selectedIter, 1); - - string userName = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[0]; - string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1]; - - // Unselect the first user. - _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); - _tableStore.SetValue(firstIter, 0, false); - _tableStore.SetValue(firstIter, 3, Gdk.RGBA.Zero); - - // Set new informations. - _tableStore.SetValue(selectedIter, 0, true); - - _selectedUserImage.Pixbuf = userPicture; - _selectedUserNameEntry.Text = userName; - _selectedUserIdLabel.Text = userId; - _saveProfileNameButton.Sensitive = false; - - // Open the selected one. - _accountManager.OpenUser(new UserId(userId)); - - _deleteButton.Sensitive = userId != AccountManager.DefaultUserId.ToString(); - - _tableStore.SetValue(selectedIter, 3, _selectedColor); - } - - private void SelectedUserNameEntry_KeyReleaseEvent(object o, KeyReleaseEventArgs args) - { - if (_saveProfileNameButton.Sensitive == false) - { - _saveProfileNameButton.Sensitive = true; - } - } - - private void AddButton_Pressed(object sender, EventArgs e) - { - _tempNewProfileName = GtkDialog.CreateInputDialog(this, "Choose the Profile Name", "Please Enter a Profile Name", MaxProfileNameLength); - - if (_tempNewProfileName != "") - { - SelectProfileImage(true); - - if (_bufferImageProfile != null) - { - AddUser(); - } - } - } - - private void DeleteButton_Pressed(object sender, EventArgs e) - { - if (GtkDialog.CreateChoiceDialog("Delete User Profile", "Are you sure you want to delete the profile ?", "Deleting this profile will also delete all associated save data.")) - { - _accountManager.DeleteUser(GetSelectedUserId()); - - RefreshList(); - } - } - - private void EditProfileNameButton_Pressed(object sender, EventArgs e) - { - _saveProfileNameButton.Sensitive = false; - - _accountManager.SetUserName(GetSelectedUserId(), _selectedUserNameEntry.Text); - - RefreshList(); - } - - private void ProcessProfileImage(byte[] buffer) - { - using Image image = Image.Load(buffer); - - image.Mutate(x => x.Resize(256, 256)); - - using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); - - image.SaveAsJpeg(streamJpg); - - _bufferImageProfile = streamJpg.ToArray(); - } - - private void ProfileImageFileChooser() - { - FileChooserNative fileChooser = new("Import Custom Profile Image", this, FileChooserAction.Open, "Import", "Cancel") - { - SelectMultiple = false, - }; - - FileFilter filter = new() - { - Name = "Custom Profile Images", - }; - filter.AddPattern("*.jpg"); - filter.AddPattern("*.jpeg"); - filter.AddPattern("*.png"); - filter.AddPattern("*.bmp"); - - fileChooser.AddFilter(filter); - - if (fileChooser.Run() == (int)ResponseType.Accept) - { - ProcessProfileImage(File.ReadAllBytes(fileChooser.Filename)); - } - - fileChooser.Dispose(); - } - - private void SelectProfileImage(bool newUser = false) - { - if (_contentManager.GetCurrentFirmwareVersion() == null) - { - ProfileImageFileChooser(); - } - else - { - Dictionary<int, string> buttons = new() - { - { 0, "Import Image File" }, - { 1, "Select Firmware Avatar" }, - }; - - ResponseType responseDialog = GtkDialog.CreateCustomDialog("Profile Image Selection", - "Choose a Profile Image", - "You may import a custom profile image, or select an avatar from the system firmware.", - buttons, MessageType.Question); - - if (responseDialog == 0) - { - ProfileImageFileChooser(); - } - else if (responseDialog == (ResponseType)1) - { - AvatarWindow avatarWindow = new() - { - NewUser = newUser, - }; - - avatarWindow.DeleteEvent += AvatarWindow_DeleteEvent; - - avatarWindow.SetSizeRequest((int)(avatarWindow.DefaultWidth * Program.WindowScaleFactor), (int)(avatarWindow.DefaultHeight * Program.WindowScaleFactor)); - avatarWindow.Show(); - } - } - } - - private void ChangeProfileImageButton_Pressed(object sender, EventArgs e) - { - if (_contentManager.GetCurrentFirmwareVersion() != null) - { - _avatarsPreloadingEvent.WaitOne(); - } - - SelectProfileImage(); - - if (_bufferImageProfile != null) - { - SetUserImage(); - } - } - - private void AvatarWindow_DeleteEvent(object sender, DeleteEventArgs args) - { - _bufferImageProfile = ((AvatarWindow)sender).SelectedProfileImage; - - if (_bufferImageProfile != null) - { - if (((AvatarWindow)sender).NewUser) - { - AddUser(); - } - else - { - SetUserImage(); - } - } - } - - private void AddUser() - { - _accountManager.AddUser(_tempNewProfileName, _bufferImageProfile); - - _bufferImageProfile = null; - _tempNewProfileName = ""; - - RefreshList(); - } - - private void SetUserImage() - { - _accountManager.SetUserImage(GetSelectedUserId(), _bufferImageProfile); - - _bufferImageProfile = null; - - RefreshList(); - } - - private UserId GetSelectedUserId() - { - if (_usersTreeView.Model.GetIterFirst(out TreeIter iter)) - { - do - { - if ((bool)_tableStore.GetValue(iter, 0)) - { - break; - } - } - while (_usersTreeView.Model.IterNext(ref iter)); - } - - return new UserId(_tableStore.GetValue(iter, 2).ToString().Split("\n")[1]); - } - - private void CloseButton_Pressed(object sender, EventArgs e) - { - Close(); - } - } -} diff --git a/src/Ryujinx/app.manifest b/src/Ryujinx/app.manifest new file mode 100644 index 00000000..920136fb --- /dev/null +++ b/src/Ryujinx/app.manifest @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> + <assemblyIdentity version="1.0.0.0" name="Ryujinx.Emulator.Avalonia"/> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 & 11 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> + </application> + </compatibility> +</assembly> -- cgit v1.2.3-70-g09d2